From 6a887f5bf3b2e30a512337a1cf658b5760423003 Mon Sep 17 00:00:00 2001 From: JoC0de <53140583+JoC0de@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:37:29 +0200 Subject: [PATCH 1/3] only generate .sln on session start + fix nullable-adding BOM handeling --- .../Tests/SourceCodeFilesHandlerTest.cs | 27 +++++++++ .../Assets/Editor/MenuItemProvider.cs | 32 +++++++++-- .../Assets/Editor/ProjectFileGeneratorBase.cs | 57 +------------------ .../Assets/Editor/ProjectFileParser.cs | 54 ++++++++++++++++++ .../Assets/Editor/SourceCodeFilesHandler.cs | 47 ++++++++++++--- .../Editor/VisualStudioAssetPostprocessor.cs | 16 +++++- 6 files changed, 162 insertions(+), 71 deletions(-) diff --git a/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs b/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs index 62e77ab..cf350e9 100644 --- a/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs +++ b/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs @@ -2,6 +2,8 @@ using System; using System.IO; +using System.Linq; +using System.Text; using NUnit.Framework; namespace UnityVisualStudioSolutionGenerator.Tests @@ -49,6 +51,7 @@ public class SourceCodeFilesHandlerTest [TestCase("#nullable enable\n", "#nullable enable\n")] [TestCase("#nullable enable\r\n", "#nullable enable\r\n")] [TestCase("#nullable enable\n\npublic class Test\n{\n}\n", "#nullable enable\n\npublic class Test\n{\n}\n")] + [TestCase("public class Test\n{\n}\n", "#nullable enable\n\npublic class Test\n{\n}\n")] public void SourceCodeFilesHandlerSimpleTest(string testSourceCode, string expectedSourceCode) { if (!testSourceCode.Contains('\n', StringComparison.Ordinal)) @@ -118,5 +121,29 @@ public void SourceCodeFilesHandlerMultiFileTest(string testSourceCode, string ex File.Delete(testSourceCode2FilePath); } } + + [Test] + public void TestByteOrderMaskHandling([Values] bool withBom, [Values] bool alreadyHasNullable) + { + const string testSourceCode1FilePath = "TestSourceCode.cs"; + var utf8Encoding = new UTF8Encoding(withBom); + try + { + const string contentWithNullable = "#nullable enable\n\npublic class Test\n{\n}\n"; + File.WriteAllText(testSourceCode1FilePath, alreadyHasNullable ? contentWithNullable : "public class Test\n{\n}\n", utf8Encoding); + SourceCodeFilesHandler.AddNullableToFile(testSourceCode1FilePath); + var expected = utf8Encoding.GetBytes(contentWithNullable); + if (withBom) + { + expected = Encoding.UTF8.GetPreamble().Concat(expected).ToArray(); + } + + Assert.That(File.ReadAllBytes(testSourceCode1FilePath), Is.EqualTo(expected)); + } + finally + { + File.Delete(testSourceCode1FilePath); + } + } } } diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/MenuItemProvider.cs b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/MenuItemProvider.cs index 7acd32b..b13a0c6 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/MenuItemProvider.cs +++ b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/MenuItemProvider.cs @@ -30,14 +30,34 @@ public static bool OpenSolutionEnabled() return GeneratorSettings.IsVisualStudioEditorEnabled(); } + /// + /// Regenerates the Visual Studio solution file and the C# project files. + /// + [MenuItem("Visual Studio/Generate Solution", priority = 1)] + public static void SyncSolution() + { + VisualStudioAssetPostprocessor.MarkAsChanged(); + CodeEditor.CurrentEditor.SyncAll(); + } + + /// + /// Returns whether or not the menu item should be enabled. + /// + /// True if the menu item should be enabled, False otherwise. + [MenuItem("Visual Studio/Generate Solution", true)] + public static bool SyncSolutionEnabled() + { + return GeneratorSettings.IsSolutionGeneratorEnabled(); + } + /// /// Regenerates the Visual Studio solution file and the C# project files as SDK-style projects. /// - [MenuItem("Visual Studio/Generate Solution (Sdk-Style)", priority = 1)] + [MenuItem("Visual Studio/Generate Solution (Sdk-Style)", priority = 2)] public static void SyncSolutionSdkStyle() { GeneratorSettings.GenerateSdkStyleProjects = true; - CodeEditor.CurrentEditor.SyncAll(); + SyncSolution(); } /// @@ -53,11 +73,11 @@ public static bool SyncSolutionSdkStyleEnabled() /// /// Regenerates the Visual Studio solution file and the C# project files as Legacy-style projects. /// - [MenuItem("Visual Studio/Generate Solution (Legacy-Style)", priority = 2)] + [MenuItem("Visual Studio/Generate Solution (Legacy-Style)", priority = 3)] public static void SyncSolutionLegacyStyle() { GeneratorSettings.GenerateSdkStyleProjects = false; - CodeEditor.CurrentEditor.SyncAll(); + SyncSolution(); } /// @@ -73,7 +93,7 @@ public static bool SyncSolutionLegacyStyleEnabled() /// /// Checks all '.cs' files to contain '#nullable enable' at the start. /// - [MenuItem("Visual Studio/Apply enable nullable to all files", priority = 3)] + [MenuItem("Visual Studio/Apply enable nullable to all files", priority = 4)] public static void EnableNullableOnAllFiles() { SourceCodeFilesHandler.EnableNullableOnAllFiles(SolutionFile.CurrentProjectSolution); @@ -92,7 +112,7 @@ public static bool EnableNullableOnAllFilesEnabled() /// /// Opens the solution generation preferences page. /// - [MenuItem("Visual Studio/Preferences", priority = 4)] + [MenuItem("Visual Studio/Preferences", priority = 5)] public static void OpenPreferences() { SettingsService.OpenProjectSettings(GeneratorSettingsProvider.PreferencesPath); diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileGeneratorBase.cs b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileGeneratorBase.cs index 569baeb..1d5e754 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileGeneratorBase.cs +++ b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileGeneratorBase.cs @@ -16,8 +16,6 @@ namespace UnityVisualStudioSolutionGenerator /// public abstract class ProjectFileGeneratorBase : ProjectFileParser { - private string? assemblyDefinitionFilePath; - /// /// Initializes a new instance of the class. /// @@ -33,45 +31,8 @@ protected ProjectFileGeneratorBase(string filePath) /// protected string ProjectName { get; } - /// - /// Gets the path of the assembly definition file associated with this project. It is extracted from the project file generated by Unity. - /// - protected string AssemblyDefinitionFilePath - { - get - { - assemblyDefinitionFilePath ??= ExtractAssemblyDefinitionFilePath(); - return assemblyDefinitionFilePath; - } - } - private string ProjectOutputFilePath => Path.ChangeExtension(AssemblyDefinitionFilePath, ".csproj"); - /// - /// Determines whether the project is from the package cache. - /// - /// True if the project file is from a package, False otherwise. - public bool IsProjectFromPackageCache() - { - if (assemblyDefinitionFilePath is not null) - { - return IsProjectFileFromPackageCache(assemblyDefinitionFilePath); - } - - var anyProjectFilePath = ProjectElement.Descendants(XmlNamespace + "None") - .Concat(ProjectElement.Descendants(XmlNamespace + "Compile")) - .Select(element => element.Attribute("Include")?.Value) - .FirstOrDefault(itemPath => itemPath is not null); - - if (anyProjectFilePath is not null) - { - return IsProjectFileFromPackageCache(Path.GetFullPath(anyProjectFilePath, DirectoryPath)); - } - - LogHelper.LogWarning($"The project file: {FilePath} has no content so skipping it."); - return true; - } - /// /// Writes the project file to disk inside . /// @@ -114,7 +75,8 @@ internal static ProjectFileGeneratorBase Create(string filePath) } /// - /// Determines the root directory that contains all project files, the directory that contains the . + /// Determines the root directory that contains all project files, the directory that contains the + /// . /// /// The absolute path to the project root directory. internal string GetProjectRootDirectoryPath() @@ -223,21 +185,6 @@ private static bool MatchesPattern(string value, IReadOnlyList pattern) return true; } - private string ExtractAssemblyDefinitionFilePath() - { - var assemblyDefinitionFilePaths = ProjectElement.Descendants(XmlNamespace + "None") - .Select(noneElement => noneElement.Attribute("Include")?.Value) - .Where(noneItemPath => noneItemPath?.EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase) == true) - .ToList(); - if (assemblyDefinitionFilePaths.Count != 1) - { - throw new InvalidOperationException( - $"The csproj file '{FilePath}' need to have exactly one '.asmdef' file but it has ['{string.Join("', '", assemblyDefinitionFilePaths)}']"); - } - - return Path.GetFullPath(assemblyDefinitionFilePaths[0], DirectoryPath); - } - private void RemoveExcludedAnalyzers() { var excludedAnalyzers = GeneratorSettings.ExcludedAnalyzers; diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileParser.cs b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileParser.cs index a588454..98b4907 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileParser.cs +++ b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/ProjectFileParser.cs @@ -13,6 +13,8 @@ namespace UnityVisualStudioSolutionGenerator /// public class ProjectFileParser { + private string? assemblyDefinitionFilePath; + /// /// Initializes a new instance of the class. /// @@ -34,6 +36,18 @@ public ProjectFileParser(string filePath) Path.GetDirectoryName(filePath) ?? throw new InvalidOperationException($"Failed to get directory path of '{filePath}'")); } + /// + /// Gets the path of the assembly definition file associated with this project. It is extracted from the project file generated by Unity. + /// + public string AssemblyDefinitionFilePath + { + get + { + assemblyDefinitionFilePath ??= ExtractAssemblyDefinitionFilePath(); + return assemblyDefinitionFilePath; + } + } + /// /// Gets the absolute path of the project file (.csproj). /// @@ -54,6 +68,31 @@ public ProjectFileParser(string filePath) /// protected string DirectoryPath { get; } + /// + /// Determines whether the project is from the package cache. + /// + /// True if the project file is from a package, False otherwise. + public bool IsProjectFromPackageCache() + { + if (assemblyDefinitionFilePath is not null) + { + return IsProjectFileFromPackageCache(assemblyDefinitionFilePath); + } + + var anyProjectFilePath = ProjectElement.Descendants(XmlNamespace + "None") + .Concat(ProjectElement.Descendants(XmlNamespace + "Compile")) + .Select(element => element.Attribute("Include")?.Value) + .FirstOrDefault(itemPath => itemPath is not null); + + if (anyProjectFilePath is not null) + { + return IsProjectFileFromPackageCache(Path.GetFullPath(anyProjectFilePath, DirectoryPath)); + } + + LogHelper.LogWarning($"The project file: {FilePath} has no content so skipping it."); + return true; + } + /// /// Read the Project file and extract all source code files. But exclude files that are inside a 'PackageCache'. /// @@ -102,5 +141,20 @@ internal static bool IsProjectFileFromPackageCache(string projectFilePath) $"{Path.DirectorySeparatorChar}Library{Path.DirectorySeparatorChar}PackageCache{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase); } + + private string ExtractAssemblyDefinitionFilePath() + { + var assemblyDefinitionFilePaths = ProjectElement.Descendants(XmlNamespace + "None") + .Select(noneElement => noneElement.Attribute("Include")?.Value) + .Where(noneItemPath => noneItemPath?.EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase) == true) + .ToList(); + if (assemblyDefinitionFilePaths.Count != 1) + { + throw new InvalidOperationException( + $"The csproj file '{FilePath}' need to have exactly one '.asmdef' file but it has ['{string.Join("', '", assemblyDefinitionFilePaths)}']"); + } + + return Path.GetFullPath(assemblyDefinitionFilePaths[0], DirectoryPath); + } } } diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/SourceCodeFilesHandler.cs b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/SourceCodeFilesHandler.cs index ee69321..7a96516 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/SourceCodeFilesHandler.cs +++ b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/SourceCodeFilesHandler.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -32,7 +33,7 @@ public static void EnableNullableOnAllFiles(SolutionFile solutionFile) Debug.Assert(Encoding.UTF8.GetBytes("#nullable enable").SequenceEqual(EnableNullableBytes), "Wrong enableNullableBytes detected."); var (allProjects, _) = SolutionFileParser.Parse(solutionFile, false); - var enableNullableReadBuffer = new byte[EnableNullableBytes.Length + 2]; + var enableNullableReadBuffer = new byte[EnableNullableBytes.Length + Utf8BomBytes.Length]; // source code is small so we can read it into memory var fullFileReadBuffer = new MemoryStream(); @@ -41,7 +42,14 @@ public static void EnableNullableOnAllFiles(SolutionFile solutionFile) var originalProjectFilePath = Path.Combine(solutionFile.SolutionDirectoryPath, Path.GetFileName(projectFile.FilePath)); if (!File.Exists(originalProjectFilePath)) { - LogHelper.LogWarning($"Can't find original (Unity) .csproj file at: '{originalProjectFilePath}'."); + var generatedProjectFileParser = new ProjectFileParser(projectFile.FilePath); + var assemblyDefinitionContent = + JsonUtility.FromJson(File.ReadAllText(generatedProjectFileParser.AssemblyDefinitionFilePath)); + originalProjectFilePath = Path.Combine(solutionFile.SolutionDirectoryPath, $"{assemblyDefinitionContent.name}.csproj"); + if (!File.Exists(originalProjectFilePath)) + { + LogHelper.LogWarning($"Can't find original (Unity) .csproj file at: '{originalProjectFilePath}'."); + } } var projectFileParser = new ProjectFileParser(originalProjectFilePath); @@ -58,7 +66,7 @@ public static void EnableNullableOnAllFiles(SolutionFile solutionFile) /// The path to the file to add nullable setting to, if it doesn't already has it. public static void AddNullableToFile(string sourceCodeFile) { - AddNullableToFile(sourceCodeFile, new byte[EnableNullableBytes.Length + 2], new MemoryStream()); + AddNullableToFile(sourceCodeFile, new byte[EnableNullableBytes.Length + Utf8BomBytes.Length], new MemoryStream()); } private static void AddNullableToFile(string sourceCodeFile, byte[] enableNullableReadBuffer, MemoryStream fullFileReadBuffer) @@ -69,7 +77,7 @@ private static void AddNullableToFile(string sourceCodeFile, byte[] enableNullab var readCount = fileStream.Read(enableNullableReadBuffer); var readBytes = enableNullableReadBuffer.AsSpan(0, readCount); var readBytesWithoutBom = readBytes; - var hasUtf8Bom = readBytes.SequenceEqual(Utf8BomBytes); + var hasUtf8Bom = readBytes.StartsWith(Utf8BomBytes); if (hasUtf8Bom) { readBytesWithoutBom = readBytes[Utf8BomBytes.Length..]; @@ -87,7 +95,7 @@ private static void AddNullableToFile(string sourceCodeFile, byte[] enableNullab fullFileReadBuffer.Capacity = (int)fileStream.Length; } - fullFileReadBuffer.Write(readBytes); + fullFileReadBuffer.Write(readBytesWithoutBom); fileStream.CopyTo(fullFileReadBuffer); var newLineBytes = DetectNewLineBytes(fullFileReadBuffer); @@ -95,17 +103,24 @@ private static void AddNullableToFile(string sourceCodeFile, byte[] enableNullab // reset to start -> write enable nullable -> rest of file fileStream.Position = 0; + + if (hasUtf8Bom) + { + fileStream.Write(Utf8BomBytes); + } + fileStream.Write(EnableNullableBytes); - var firstCharWasNewline = readBytes.StartsWith(newLineBytes); + var firstCharWasNewline = readBytesWithoutBom.StartsWith(newLineBytes); if (!firstCharWasNewline) { fileStream.Write(newLineBytes); } - var secondCharWasNewLine = readBytes.IsEmpty || + var secondCharWasNewLine = readBytesWithoutBom.IsEmpty || firstCharWasNewline && - (readBytes[Math.Min(newLineBytes.Length, readBytes.Length)..].StartsWith(newLineBytes) || - readBytes.Length == newLineBytes.Length); + (readBytesWithoutBom[Math.Min(newLineBytes.Length, readBytesWithoutBom.Length)..] + .StartsWith(newLineBytes) || + readBytesWithoutBom.Length == newLineBytes.Length); if (!secondCharWasNewLine) { fileStream.Write(newLineBytes); @@ -143,5 +158,19 @@ private static byte[] DetectNewLineBytes(MemoryStream memoryStream) previous = readByte; } } + + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Instantiated by json-serializer.")] + private sealed class AssemblyDefinitionContent + { + [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local", Justification = "Required by serializer.")] + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Required by serializer.")] + [SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Required by serializer.")] + [SuppressMessage( + "StyleCop.CSharp.NamingRules", + "SA1307:Accessible fields should begin with upper-case letter", + Justification = "Required by serializer.")] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Required by serializer.")] + public string name = string.Empty; + } } } diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/VisualStudioAssetPostprocessor.cs b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/VisualStudioAssetPostprocessor.cs index 6445574..8fd2ee4 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/Editor/VisualStudioAssetPostprocessor.cs +++ b/src/UnityVisualStudioSolutionGenerator/Assets/Editor/VisualStudioAssetPostprocessor.cs @@ -25,6 +25,16 @@ internal class VisualStudioAssetPostprocessor : AssetPostprocessor private static DateTime lastSolutionGenerationTime; + /// + /// Clears the cached solution content so the next time the solution is generated. + /// + public static void MarkAsChanged() + { + lastSolutionGenerationTime = DateTime.MinValue; + lastInputSolutionContent = null; + lastOutputSolutionContent = null; + } + /// /// Called by unity if it reloads the assembly. /// @@ -39,13 +49,17 @@ private static void Initialize() var stopwatch = Stopwatch.StartNew(); var (allProjects, sourceContainsDuplicateProjects) = SolutionFileParser.Parse(solutionFile, false); - if (!sourceContainsDuplicateProjects) + const string isSolutionGeneratedKey = "UnityVisualStudioSolutionGenerator.IsSolutionGenerated"; + var isSolutionGenerated = SessionState.GetBool(isSolutionGeneratedKey, false); + if (!sourceContainsDuplicateProjects || isSolutionGenerated) { // if we don't call 'GenerateNewProjects' we need to ensure that all SourceCodeFileWatcher's are running ProjectSourceCodeWatcherManager.Initialize(allProjects); return; } + SessionState.SetBool(isSolutionGeneratedKey, true); + // Sometimes 'OnGeneratedCSProjectFiles' is not called when the reload order is wrong so we regenerate it here. // We detect this by checking if Unity generated the .sln and skipped all events like 'OnGeneratedCSProjectFiles' // so the .sln contains both the .csproj from Unity and the one generated by GenerateNewProjects. From 00b40bbef7a545a55671baa5bb7ae73d301853bb Mon Sep 17 00:00:00 2001 From: JoC0de <53140583+JoC0de@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:41:28 +0200 Subject: [PATCH 2/3] remove dublicat test case --- .../Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs b/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs index cf350e9..bbe3a9b 100644 --- a/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs +++ b/src/UnityVisualStudioSolutionGenerator.Tests/Assets/Editor/Tests/SourceCodeFilesHandlerTest.cs @@ -51,7 +51,6 @@ public class SourceCodeFilesHandlerTest [TestCase("#nullable enable\n", "#nullable enable\n")] [TestCase("#nullable enable\r\n", "#nullable enable\r\n")] [TestCase("#nullable enable\n\npublic class Test\n{\n}\n", "#nullable enable\n\npublic class Test\n{\n}\n")] - [TestCase("public class Test\n{\n}\n", "#nullable enable\n\npublic class Test\n{\n}\n")] public void SourceCodeFilesHandlerSimpleTest(string testSourceCode, string expectedSourceCode) { if (!testSourceCode.Contains('\n', StringComparison.Ordinal)) @@ -123,17 +122,17 @@ public void SourceCodeFilesHandlerMultiFileTest(string testSourceCode, string ex } [Test] - public void TestByteOrderMaskHandling([Values] bool withBom, [Values] bool alreadyHasNullable) + public void TestByteOrderMaskHandling([Values] bool withByteOrderMask, [Values] bool alreadyHasNullable) { const string testSourceCode1FilePath = "TestSourceCode.cs"; - var utf8Encoding = new UTF8Encoding(withBom); + var utf8Encoding = new UTF8Encoding(withByteOrderMask); try { const string contentWithNullable = "#nullable enable\n\npublic class Test\n{\n}\n"; File.WriteAllText(testSourceCode1FilePath, alreadyHasNullable ? contentWithNullable : "public class Test\n{\n}\n", utf8Encoding); SourceCodeFilesHandler.AddNullableToFile(testSourceCode1FilePath); var expected = utf8Encoding.GetBytes(contentWithNullable); - if (withBom) + if (withByteOrderMask) { expected = Encoding.UTF8.GetPreamble().Concat(expected).ToArray(); } From 8e6eb79cefef1935cb4d5b9c8a98ad035d0e74f2 Mon Sep 17 00:00:00 2001 From: JoC0de <53140583+JoC0de@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:43:12 +0200 Subject: [PATCH 3/3] change version number to 1.0.9 --- src/UnityVisualStudioSolutionGenerator/Assets/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityVisualStudioSolutionGenerator/Assets/package.json b/src/UnityVisualStudioSolutionGenerator/Assets/package.json index f5eda93..f02defa 100644 --- a/src/UnityVisualStudioSolutionGenerator/Assets/package.json +++ b/src/UnityVisualStudioSolutionGenerator/Assets/package.json @@ -1,7 +1,7 @@ { "name": "com.github-joc0de.visual-studio-solution-generator", "displayName": "Unity Visual Studio Solution Generator", - "version": "1.0.8", + "version": "1.0.9", "description": "Visual Studio Solution Generator with improved developer productivity especially when working with multi-package unity projects", "unity": "2021.2", "keywords": [