diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f1b41..a1a1395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,39 @@ # Code Editor Package for Visual Studio +## [2.0.11] - 2021-07-01 + +Integration: + +- Added support for Visual Studio and Visual Studio For Mac 2022. +- Fixed an issue when the package was enabled for background processes. + +Project generation: + +- Use absolute paths for Analyzers and rulesets. + + +## [2.0.10] - 2021-06-10 + +Project generation: + +- Improved project generation performance when a file is moved, deleted or modified. + +Integration: + +- Improved Inner-loop performance by avoiding to call the package manager when looking up `vswhere` utility. +- Fixed a network issue preventing the communication between Visual Studio and Unity on Windows. + ## [2.0.9] - 2021-05-04 Project generation: -Added support for CLI. +- Added support for CLI. Integration: -Improved performance when discovering Visual Studio installations. -Warn when legacy assemblies are present in the project. -Warn when the package version is not up-to-date. - +- Improved performance when discovering Visual Studio installations. +- Warn when legacy assemblies are present in the project. +- Warn when the package version is not up-to-date. ## [2.0.8] - 2021-04-09 diff --git a/Editor/AppleEventIntegration~/AppleEventIntegration/main.mm b/Editor/AppleEventIntegration~/AppleEventIntegration/main.mm index a53cf84..678b765 100644 --- a/Editor/AppleEventIntegration~/AppleEventIntegration/main.mm +++ b/Editor/AppleEventIntegration~/AppleEventIntegration/main.mm @@ -118,12 +118,7 @@ static BOOL ApplicationSupportsQueryOpenedSolution(NSString* appPath) return NO; NSString* version = (NSString*)versionValue; - NSArray* components = [version componentsSeparatedByString:@"."]; - if (!components || components.count < 2) - return NO; - - return [components[0] integerValue] >= 8 - && [components[1] integerValue] >= 6; + return [version compare:@"8.6" options:NSNumericSearch] != NSOrderedAscending; } static NSArray* QueryRunningInstances(NSString *appPath) diff --git a/Editor/Discovery.cs b/Editor/Discovery.cs index 6c4a676..cdb64e1 100644 --- a/Editor/Discovery.cs +++ b/Editor/Discovery.cs @@ -21,9 +21,7 @@ namespace Microsoft.Unity.VisualStudio.Editor public static void FindVSWhere() { - _vsWherePath = FileUtility - .FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe") - .FirstOrDefault(); + _vsWherePath = FileUtility.GetPackageAssetFullPath("Editor", "VSWhere", "vswhere.exe"); } public static IEnumerable GetVisualStudioInstallations() @@ -71,7 +69,12 @@ namespace Microsoft.Unity.VisualStudio.Editor // On Mac we use the .app folder, so we need to access to main assembly if (VisualStudioEditor.IsOSX) - fvi = Path.Combine(editorPath, "Contents", "Resources", "lib", "monodevelop", "bin", "VisualStudio.exe"); + { + fvi = Path.Combine(editorPath, "Contents/Resources/lib/monodevelop/bin/VisualStudio.exe"); + + if (!File.Exists(fvi)) + fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.exe"); + } if (!File.Exists(fvi)) return false; diff --git a/Editor/FileUtility.cs b/Editor/FileUtility.cs index f23b720..073d280 100644 --- a/Editor/FileUtility.cs +++ b/Editor/FileUtility.cs @@ -5,8 +5,6 @@ *--------------------------------------------------------------------------------------------*/ using System; using System.IO; -using System.Linq; -using UnityEditor; using UnityEngine; namespace Microsoft.Unity.VisualStudio.Editor @@ -16,27 +14,19 @@ namespace Microsoft.Unity.VisualStudio.Editor public const char WinSeparator = '\\'; public const char UnixSeparator = '/'; - // Safe for packages as we use packageInfo.resolvedPath, so this should work in library package cache as well - public static string[] FindPackageAssetFullPath(string assetfilter, string filefilter) + public static string GetPackageAssetFullPath(params string[] components) { - return AssetDatabase.FindAssets(assetfilter) - .Select(AssetDatabase.GUIDToAssetPath) - .Where(assetPath => assetPath.Contains(filefilter)) - .Select(asset => - { - var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset); - return Normalize(packageInfo.resolvedPath + asset.Substring(packageInfo.assetPath.Length)); - }) - .ToArray(); + // Unity has special IO handling of Packages and will resolve those path to the right package location + return Path.GetFullPath(Path.Combine("Packages", "com.unity.ide.visualstudio", Path.Combine(components))); } public static string GetAssetFullPath(string asset) { var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); - return Path.GetFullPath(Path.Combine(basePath, Normalize(asset))); + return Path.GetFullPath(Path.Combine(basePath, NormalizePathSeparators(asset))); } - public static string Normalize(string path) + public static string NormalizePathSeparators(this string path) { if (string.IsNullOrEmpty(path)) return path; @@ -65,12 +55,18 @@ namespace Microsoft.Unity.VisualStudio.Editor return relative == Path.GetFileName(relative); } + + public static string MakeAbsolutePath(this string path, string projectDirectory) + { + if (string.IsNullOrEmpty(path)) { return string.Empty; } + return Path.IsPathRooted(path) ? path : Path.Combine(projectDirectory, path); + } // returns null if outside of the project scope internal static string MakeRelativeToProjectPath(string fileName) { var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); - fileName = Normalize(fileName); + fileName = NormalizePathSeparators(fileName); if (!Path.IsPathRooted(fileName)) fileName = Path.Combine(basePath, fileName); @@ -82,6 +78,5 @@ namespace Microsoft.Unity.VisualStudio.Editor .Substring(basePath.Length) .Trim(Path.DirectorySeparatorChar); } - } } diff --git a/Editor/Messaging/Messenger.cs b/Editor/Messaging/Messenger.cs index d88071e..2d8f621 100644 --- a/Editor/Messaging/Messenger.cs +++ b/Editor/Messaging/Messenger.cs @@ -17,11 +17,31 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging private readonly object _disposeLock = new object(); private bool _disposed; +#if UNITY_EDITOR_WIN + [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, HandleFlags dwFlags); + + [Flags] + private enum HandleFlags: uint + { + None = 0, + Inherit = 1, + ProtectFromClose = 2 + } +#endif + protected Messager(int port) { _socket = new UdpSocket(); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + +#if UNITY_EDITOR_WIN + // Explicitely disable inheritance for our UDP socket handle + // We found that Unity is creating a fork when importing new assets that can clone our socket + SetHandleInformation(_socket.Handle, HandleFlags.Inherit, HandleFlags.None); +#endif + _socket.Bind(IPAddress.Any, port); BeginReceiveMessage(); @@ -72,9 +92,7 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging { message.Origin = (IPEndPoint)endPoint; - int port; - int bufferSize; - if (IsValidTcpMessage(message, out port, out bufferSize)) + if (IsValidTcpMessage(message, out var port, out var bufferSize)) { // switch to TCP mode to handle big messages TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer => diff --git a/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist b/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist index 3d8975b..6c5ba04 100644 --- a/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist +++ b/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 19H1030 + 19H1217 CFBundleDevelopmentRegion en CFBundleExecutable diff --git a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration b/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration index 3ffd3e0..f0a8a0d 100644 Binary files a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration and b/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration differ diff --git a/Editor/ProjectGeneration/ProjectGeneration.cs b/Editor/ProjectGeneration/ProjectGeneration.cs index 7a12909..cd9c311 100644 --- a/Editor/ProjectGeneration/ProjectGeneration.cs +++ b/Editor/ProjectGeneration/ProjectGeneration.cs @@ -13,9 +13,9 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Unity.CodeEditor; +using Unity.Profiling; using UnityEditor; using UnityEditor.Compilation; -using UnityEditorInternal; using UnityEngine; namespace Microsoft.Unity.VisualStudio.Editor @@ -45,9 +45,9 @@ namespace Microsoft.Unity.VisualStudio.Editor const string k_WindowsNewline = "\r\n"; - string m_SolutionProjectEntryTemplate = @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""{4}EndProject"; + const string m_SolutionProjectEntryTemplate = @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""{4}EndProject"; - string m_SolutionProjectConfigurationTemplate = string.Join("\r\n", + readonly string m_SolutionProjectConfigurationTemplate = string.Join(k_WindowsNewline, @" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", @" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU", @" {{{0}}}.Release|Any CPU.ActiveCfg = Release|Any CPU", @@ -55,10 +55,6 @@ namespace Microsoft.Unity.VisualStudio.Editor static readonly string[] k_ReimportSyncExtensions = { ".dll", ".asmdef" }; - static readonly Regex k_ScriptReferenceExpression = new Regex( - @"^Library.ScriptAssemblies.(?(?.*)\.dll$)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - string[] m_ProjectSupportedExtensions = Array.Empty(); string[] m_BuiltinSupportedExtensions = Array.Empty(); @@ -102,15 +98,50 @@ namespace Microsoft.Unity.VisualStudio.Editor /// public bool SyncIfNeeded(IEnumerable affectedFiles, IEnumerable reimportedFiles) { - SetupProjectSupportedExtensions(); - - // Don't sync if we haven't synced before - if (HasSolutionBeenGenerated() && HasFilesBeenModified(affectedFiles, reimportedFiles)) + using (solutionSyncMarker.Auto()) { - Sync(); + SetupProjectSupportedExtensions(); + + // See https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/ + // We create a .vsconfig file to make sure our ManagedGame workload is installed + CreateVsConfigIfNotFound(); + + // Don't sync if we haven't synced before + var affected = affectedFiles as ICollection ?? affectedFiles.ToArray(); + var reimported = reimportedFiles as ICollection ?? reimportedFiles.ToArray(); + if (!HasFilesBeenModified(affected, reimported)) + { + return false; + } + + var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution); + var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList(); + SyncSolution(allProjectAssemblies); + + var allAssetProjectParts = GenerateAllAssetProjectParts(); + + var affectedNames = affected + .Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)) + .Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => + name.Split(new[] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]); + var reimportedNames = reimported + .Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)) + .Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => + name.Split(new[] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]); + var affectedAndReimported = new HashSet(affectedNames.Concat(reimportedNames)); + + foreach (var assembly in allProjectAssemblies) + { + if (!affectedAndReimported.Contains(assembly.name)) + continue; + + SyncProject(assembly, + allAssetProjectParts, + responseFilesData: ParseResponseFileData(assembly).ToArray()); + } + return true; } - return false; } private void CreateVsConfigIfNotFound() @@ -123,10 +154,10 @@ namespace Microsoft.Unity.VisualStudio.Editor var content = $@"{{ ""version"": ""1.0"", - ""components"": [ + ""components"": [ ""{Discovery.ManagedWorkload}"" ] -}} +}} "; m_FileIOProvider.WriteAllText(vsConfigFile, content); } @@ -151,6 +182,8 @@ namespace Microsoft.Unity.VisualStudio.Editor editor?.TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, searchInstallations: true, out m_CurrentInstallation); } + static ProfilerMarker solutionSyncMarker = new ProfilerMarker("SolutionSynchronizerSync"); + public void Sync() { // We need the exact VS version/capabilities to tweak project generation (analyzers/langversion) @@ -170,6 +203,7 @@ namespace Microsoft.Unity.VisualStudio.Editor { GenerateAndWriteSolutionAndProjects(); } + OnGeneratedCSProjectFiles(); } @@ -246,27 +280,19 @@ namespace Microsoft.Unity.VisualStudio.Editor { // Only synchronize assemblies that have associated source files and ones that we actually want in the project. // This also filters out DLLs coming from .asmdef files in packages. - var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution); + var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution).ToList(); var allAssetProjectParts = GenerateAllAssetProjectParts(); - var assemblyList = assemblies.ToList(); + SyncSolution(assemblies); - SyncSolution(assemblyList); + var allProjectAssemblies = RelevantAssembliesForMode(assemblies); - var allProjectAssemblies = RelevantAssembliesForMode(assemblyList).ToList(); - - foreach (Assembly assembly in allProjectAssemblies) + foreach (var assembly in allProjectAssemblies) { SyncProject(assembly, allAssetProjectParts, - responseFilesData: ParseResponseFileData(assembly), - allProjectAssemblies, -#if UNITY_2020_2_OR_NEWER - assembly.compilerOptions.RoslynAnalyzerDllPaths); -#else - Array.Empty()); -#endif + responseFilesData: ParseResponseFileData(assembly).ToArray()); } } @@ -310,7 +336,7 @@ namespace Microsoft.Unity.VisualStudio.Editor if (IsSupportedFile(asset) && ScriptingLanguage.None == ScriptingLanguageFor(asset)) { // Find assembly the asset belongs to by adding script extension and using compilation pipeline. - var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs"); + var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset); if (string.IsNullOrEmpty(assemblyName)) { @@ -340,13 +366,11 @@ namespace Microsoft.Unity.VisualStudio.Editor private void SyncProject( Assembly assembly, Dictionary allAssetsProjectParts, - IEnumerable responseFilesData, - List allProjectAssemblies, - string[] roslynAnalyzerDllPaths) + ResponseFileData[] responseFilesData) { SyncProjectFileIfNotChanged( ProjectFile(assembly), - ProjectText(assembly, allAssetsProjectParts, responseFilesData, allProjectAssemblies, roslynAnalyzerDllPaths)); + ProjectText(assembly, allAssetsProjectParts, responseFilesData)); } private void SyncProjectFileIfNotChanged(string path, string newContents) @@ -444,11 +468,9 @@ namespace Microsoft.Unity.VisualStudio.Editor private string ProjectText(Assembly assembly, Dictionary allAssetsProjectParts, - IEnumerable responseFilesData, - List allProjectAssemblies, - string[] roslynAnalyzerDllPaths) + ResponseFileData[] responseFilesData) { - var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData, roslynAnalyzerDllPaths)); + var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData)); var references = new List(); projectBuilder.Append(@" ").Append(k_WindowsNewline); @@ -485,12 +507,12 @@ namespace Microsoft.Unity.VisualStudio.Editor var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r)); var internalAssemblyReferences = assembly.assemblyReferences - .Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath); + .Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath); var allReferences = - assembly.compiledAssemblyReferences - .Union(responseRefs) - .Union(references) - .Union(internalAssemblyReferences); + assembly.compiledAssemblyReferences + .Union(responseRefs) + .Union(references) + .Union(internalAssemblyReferences); foreach (var reference in allReferences) { @@ -503,7 +525,7 @@ namespace Microsoft.Unity.VisualStudio.Editor if (0 < assembly.assemblyReferences.Length) { projectBuilder.Append(" ").Append(k_WindowsNewline); - foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution))) + foreach (var reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution))) { projectBuilder.Append(" ").Append(k_WindowsNewline); projectBuilder.Append(" {").Append(ProjectGuid(reference)).Append("}").Append(k_WindowsNewline); @@ -514,7 +536,7 @@ namespace Microsoft.Unity.VisualStudio.Editor projectBuilder.Append(@" ").Append(k_WindowsNewline); } - projectBuilder.Append(ProjectFooter()); + projectBuilder.Append(GetProjectFooter()); return projectBuilder.ToString(); } @@ -548,29 +570,20 @@ namespace Microsoft.Unity.VisualStudio.Editor } private static readonly Regex InvalidCharactersRegexPattern = new Regex(@"\?|&|\*|""|<|>|\||#|%|\^|;" + (VisualStudioEditor.IsWindows ? "" : "|:")); + public string SolutionFile() { - return Path.Combine(FileUtility.Normalize(ProjectDirectory), $"{InvalidCharactersRegexPattern.Replace(m_ProjectName, "_")}.sln"); + return Path.Combine(ProjectDirectory.NormalizePathSeparators(), $"{InvalidCharactersRegexPattern.Replace(m_ProjectName, "_")}.sln"); } internal string VsConfigFile() { - return Path.Combine(FileUtility.Normalize(ProjectDirectory), ".vsconfig"); + return Path.Combine(ProjectDirectory.NormalizePathSeparators(), ".vsconfig"); } - private string ProjectHeader( - Assembly assembly, - IEnumerable responseFilesData, - string[] roslynAnalyzerDllPaths - ) + internal string GetLangVersion(Assembly assembly) { - var toolsVersion = "4.0"; - var productVersion = "10.0.20506"; - const string baseDirectory = "."; - - var targetFrameworkVersion = "v4.7.1"; var targetLanguageVersion = "latest"; // danger: latest is not the same absolute value depending on the VS version. - if (m_CurrentInstallation != null) { var vsLanguageSupport = m_CurrentInstallation.LatestLanguageVersionSupported; @@ -580,43 +593,48 @@ namespace Microsoft.Unity.VisualStudio.Editor targetLanguageVersion = (vsLanguageSupport <= unityLanguageSupport ? vsLanguageSupport : unityLanguageSupport).ToString(2); // (major, minor) only } + return targetLanguageVersion; + } + + private string ProjectHeader( + Assembly assembly, + ResponseFileData[] responseFilesData + ) + { var projectType = ProjectTypeOf(assembly.name); + string rulesetPath = null; + var analyzers = Array.Empty(); - var arguments = new object[] - { - toolsVersion, - productVersion, - ProjectGuid(assembly), - XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEngineAssemblyPath())), - XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEditorAssemblyPath())), - string.Join(";", assembly.defines.Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()), - MSBuildNamespaceUri, - assembly.name, - assembly.outputPath, - GetRootNamespace(assembly), - targetFrameworkVersion, - targetLanguageVersion, - baseDirectory, - assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe), - // flavoring - projectType + ":" + (int)projectType, - EditorUserBuildSettings.activeBuildTarget + ":" + (int)EditorUserBuildSettings.activeBuildTarget, - Application.unityVersion, - VisualStudioIntegration.PackageVersion() - }; - - try + if (m_CurrentInstallation != null && m_CurrentInstallation.SupportsAnalyzers) { + analyzers = m_CurrentInstallation.GetAnalyzers(); #if UNITY_2020_2_OR_NEWER - return string.Format(GetProjectHeaderTemplate(roslynAnalyzerDllPaths, assembly.compilerOptions.RoslynAnalyzerRulesetPath), arguments); -#else - return string.Format(GetProjectHeaderTemplate(Array.Empty(), null), arguments); + analyzers = analyzers != null ? analyzers.Concat(assembly.compilerOptions.RoslynAnalyzerDllPaths).ToArray() : assembly.compilerOptions.RoslynAnalyzerDllPaths; + rulesetPath = assembly.compilerOptions.RoslynAnalyzerRulesetPath; #endif } - catch (Exception) + + var projectProperties = new ProjectProperties() { - throw new NotSupportedException("Failed creating c# project because the c# project header did not have the correct amount of arguments, which is " + arguments.Length); - } + ProjectGuid = ProjectGuid(assembly), + LangVersion = GetLangVersion(assembly), + AssemblyName = assembly.name, + RootNamespace = GetRootNamespace(assembly), + OutputPath = assembly.outputPath, + // Analyzers + Analyzers = analyzers, + RulesetPath = rulesetPath, + // RSP alterable + Defines = assembly.defines.Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray(), + Unsafe = assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe), + // VSTU Flavoring + FlavoringProjectType = projectType + ":" + (int)projectType, + FlavoringBuildTarget = EditorUserBuildSettings.activeBuildTarget + ":" + (int)EditorUserBuildSettings.activeBuildTarget, + FlavoringUnityVersion = Application.unityVersion, + FlavoringPackageVersion = VisualStudioIntegration.PackageVersion(), + }; + + return GetProjectHeader(projectProperties); } private enum ProjectType @@ -642,9 +660,123 @@ namespace Microsoft.Unity.VisualStudio.Editor return ProjectType.Game; } + private string GetProjectHeader(ProjectProperties properties) + { + var header = new[] + { + $@"", + $@"", + $@" ", + $@" {properties.LangVersion}", + $@" ", + $@" ", + $@" Debug", + $@" AnyCPU", + $@" 10.0.20506", + $@" 2.0", + $@" {properties.RootNamespace}", + $@" {{{properties.ProjectGuid}}}", + $@" Library", + $@" Properties", + $@" {properties.AssemblyName}", + $@" v4.7.1", + $@" 512", + $@" .", + $@" ", + $@" ", + $@" true", + $@" full", + $@" false", + $@" {properties.OutputPath}", + $@" {string.Join(";", properties.Defines)}", + $@" prompt", + $@" 4", + $@" 0169", + $@" {properties.Unsafe}", + $@" ", + $@" ", + $@" pdbonly", + $@" true", + $@" Temp\bin\Release\", + $@" prompt", + $@" 4", + $@" 0169", + $@" {properties.Unsafe}", + $@" " + }; + + var forceExplicitReferences = new[] + { + $@" ", + $@" true", + $@" true", + $@" false", + $@" false", + $@" false", + $@" " + }; + + var flavoring = new[] + { + $@" ", + $@" {{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}", + $@" Package", + $@" {properties.FlavoringPackageVersion}", + $@" {properties.FlavoringProjectType}", + $@" {properties.FlavoringBuildTarget}", + $@" {properties.FlavoringUnityVersion}", + $@" " + }; + + var footer = new[] + { + @"" + }; + + var lines = header + .Concat(forceExplicitReferences) + .Concat(flavoring) + .ToList(); + + if (!string.IsNullOrEmpty(properties.RulesetPath)) + { + lines.Add(@" "); + lines.Add($" {properties.RulesetPath.MakeAbsolutePath(ProjectDirectory).NormalizePathSeparators()}"); + lines.Add(@" "); + } + + if (properties.Analyzers.Any()) + { + lines.Add(@" "); + foreach (var analyzer in properties.Analyzers) + { + lines.Add($@" "); + } + lines.Add(@" "); + } + + return string.Join(k_WindowsNewline, lines.Concat(footer)); + } + + private static string GetProjectFooter() + { + return string.Join(k_WindowsNewline, + @" ", + @" ", + @" ", + @"", + @""); + } + private static string GetSolutionText() { - return string.Join("\r\n", + return string.Join(k_WindowsNewline, @"", @"Microsoft Visual Studio Solution File, Format Version {0}", @"# Visual Studio {1}", @@ -662,126 +794,6 @@ namespace Microsoft.Unity.VisualStudio.Editor @"").Replace(" ", "\t"); } - private static string GetProjectFooterTemplate() - { - return string.Join("\r\n", - @" ", - @" ", - @" ", - @"", - @""); - } - - private string GetProjectHeaderTemplate(string[] roslynAnalyzerDllPaths, string roslynAnalyzerRulesetPath) - { - var header = new[] - { - @"", - @"", - @" ", - @" {11}", - @" ", - @" ", - @" Debug", - @" AnyCPU", - @" {1}", - @" 2.0", - @" {9}", - @" {{{2}}}", - @" Library", - @" Properties", - @" {7}", - @" {10}", - @" 512", - @" {12}", - @" ", - @" ", - @" true", - @" full", - @" false", - @" {8}", - @" {5}", - @" prompt", - @" 4", - @" 0169", - @" {13}", - @" ", - @" ", - @" pdbonly", - @" true", - @" Temp\bin\Release\", - @" prompt", - @" 4", - @" 0169", - @" {13}", - @" " - }; - - var forceExplicitReferences = new[] - { - @" ", - @" true", - @" true", - @" false", - @" false", - @" false", - @" " - }; - - var flavoring = new[] - { - @" ", - @" {{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}", - @" Package", - @" {17}", - @" {14}", - @" {15}", - @" {16}", - @" " - }; - - var footer = new[] - { - @"" - }; - - var lines = header.Concat(forceExplicitReferences).Concat(flavoring).ToList(); - - // Only add analyzer block for compatible Visual Studio - if (m_CurrentInstallation != null && m_CurrentInstallation.SupportsAnalyzers) - { -#if UNITY_2020_2_OR_NEWER - if (roslynAnalyzerRulesetPath != null) - { - lines.Add(@" "); - lines.Add($" {roslynAnalyzerRulesetPath}"); - lines.Add(@" "); - } -#endif - - string[] analyzers = m_CurrentInstallation.GetAnalyzers(); - string[] allAnalyzers = analyzers != null ? analyzers.Concat(roslynAnalyzerDllPaths).ToArray() : roslynAnalyzerDllPaths; - - if (allAnalyzers.Any()) - { - lines.Add(@" "); - foreach (var analyzer in allAnalyzers) - { - lines.Add($@" "); - } - lines.Add(@" "); - } - } - - return string.Join("\r\n", lines.Concat(footer)); - } - private void SyncSolution(IEnumerable assemblies) { if (InvalidCharactersRegexPattern.IsMatch(ProjectDirectory)) @@ -837,7 +849,7 @@ namespace Microsoft.Unity.VisualStudio.Editor if (array == null || array.Length == 0) { // HideSolution by default - array = new SolutionProperties[] { + array = new [] { new SolutionProperties() { Name = "SolutionProperties", Type = "preSolution", @@ -908,8 +920,8 @@ namespace Microsoft.Unity.VisualStudio.Editor private string EscapedRelativePathFor(string file) { - var projectDir = FileUtility.Normalize(ProjectDirectory); - file = FileUtility.Normalize(file); + var projectDir = ProjectDirectory.NormalizePathSeparators(); + file = file.NormalizePathSeparators(); var path = SkipPathPrefix(file, projectDir); var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/')); @@ -917,7 +929,7 @@ namespace Microsoft.Unity.VisualStudio.Editor { // We have to normalize the path, because the PackageManagerRemapper assumes // dir seperators will be os specific. - var absolutePath = Path.GetFullPath(FileUtility.Normalize(path)); + var absolutePath = Path.GetFullPath(path.NormalizePathSeparators()); path = SkipPathPrefix(absolutePath, projectDir); } @@ -931,11 +943,6 @@ namespace Microsoft.Unity.VisualStudio.Editor return path; } - private static string ProjectFooter() - { - return GetProjectFooterTemplate(); - } - static string GetProjectExtension() { return ".csproj"; @@ -956,7 +963,7 @@ namespace Microsoft.Unity.VisualStudio.Editor private static string GetRootNamespace(Assembly assembly) { #if UNITY_2020_2_OR_NEWER - return assembly.rootNamespace; + return assembly.rootNamespace; #else return EditorSettings.projectGenerationRootNamespace; #endif diff --git a/Editor/ProjectGeneration/ProjectProperties.cs b/Editor/ProjectGeneration/ProjectProperties.cs new file mode 100644 index 0000000..6438f24 --- /dev/null +++ b/Editor/ProjectGeneration/ProjectProperties.cs @@ -0,0 +1,27 @@ +using System; + +namespace Microsoft.Unity.VisualStudio.Editor +{ + internal class ProjectProperties + { + public string ProjectGuid { get; set; } = string.Empty; + public string LangVersion { get; set; } = "latest"; + public string AssemblyName { get; set; } = string.Empty; + public string RootNamespace { get; set; } = string.Empty; + public string OutputPath { get; set; } = string.Empty; + + // Analyzers + public string[] Analyzers { get; set; } = Array.Empty(); + public string RulesetPath { get; set; } = string.Empty; + + // RSP alterable + public string[] Defines { get; set; } = Array.Empty(); + public bool Unsafe { get; set; } = false; + + // VSTU Flavouring + public string FlavoringProjectType { get; set; } = string.Empty; + public string FlavoringBuildTarget { get; set; } = string.Empty; + public string FlavoringUnityVersion { get; set; } = string.Empty; + public string FlavoringPackageVersion { get; set; } = string.Empty; + } +} diff --git a/Editor/ProjectGeneration/ProjectProperties.cs.meta b/Editor/ProjectGeneration/ProjectProperties.cs.meta new file mode 100644 index 0000000..d8a750a --- /dev/null +++ b/Editor/ProjectGeneration/ProjectProperties.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa7011e2ea1ff024083fea2179f3df08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Testing/TestAdaptor.cs b/Editor/Testing/TestAdaptor.cs index 63b5320..08238e3 100644 --- a/Editor/Testing/TestAdaptor.cs +++ b/Editor/Testing/TestAdaptor.cs @@ -30,7 +30,7 @@ namespace Microsoft.Unity.VisualStudio.Editor.Testing FullName = testAdaptor.FullName; Type = testAdaptor.TypeInfo?.FullName; - Method = testAdaptor?.Method?.Name; + Method = testAdaptor.Method?.Name; Assembly = testAdaptor.TypeInfo?.Assembly?.Location; Parent = parent; diff --git a/Editor/Testing/TestRunnerApiListener.cs b/Editor/Testing/TestRunnerApiListener.cs index a4db5f0..63198ca 100644 --- a/Editor/Testing/TestRunnerApiListener.cs +++ b/Editor/Testing/TestRunnerApiListener.cs @@ -8,11 +8,14 @@ namespace Microsoft.Unity.VisualStudio.Editor.Testing [InitializeOnLoad] internal class TestRunnerApiListener { - private static TestRunnerApi _testRunnerApi; - private static TestRunnerCallbacks _testRunnerCallbacks; + private static readonly TestRunnerApi _testRunnerApi; + private static readonly TestRunnerCallbacks _testRunnerCallbacks; static TestRunnerApiListener() { + if (!VisualStudioEditor.IsEnabled) + return; + _testRunnerApi = ScriptableObject.CreateInstance(); _testRunnerCallbacks = new TestRunnerCallbacks(); @@ -26,7 +29,7 @@ namespace Microsoft.Unity.VisualStudio.Editor.Testing private static void RetrieveTestList(TestMode mode) { - _testRunnerApi.RetrieveTestList(mode, (ta) => _testRunnerCallbacks.TestListRetrieved(mode, ta)); + _testRunnerApi?.RetrieveTestList(mode, ta => _testRunnerCallbacks.TestListRetrieved(mode, ta)); } public static void ExecuteTests(string command) @@ -41,12 +44,12 @@ namespace Microsoft.Unity.VisualStudio.Editor.Testing var testMode = (TestMode)Enum.Parse(typeof(TestMode), command.Substring(0, index)); var filter = command.Substring(index + 1); - ExecuteTests(new Filter() { testMode = testMode, testNames = new string[] { filter } }); + ExecuteTests(new Filter { testMode = testMode, testNames = new [] { filter } }); } private static void ExecuteTests(Filter filter) { - _testRunnerApi.Execute(new ExecutionSettings(filter)); + _testRunnerApi?.Execute(new ExecutionSettings(filter)); } } } diff --git a/Editor/UnityInstallation.cs b/Editor/UnityInstallation.cs index 74797cd..89f1793 100644 --- a/Editor/UnityInstallation.cs +++ b/Editor/UnityInstallation.cs @@ -10,6 +10,33 @@ namespace Microsoft.Unity.VisualStudio.Editor { internal static class UnityInstallation { + public static bool IsMainUnityEditorProcess + { + get + { +#if UNITY_2020_2_OR_NEWER + if (UnityEditor.AssetDatabase.IsAssetImportWorkerProcess()) + return false; +#elif UNITY_2019_3_OR_NEWER + if (UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess()) + return false; +#endif + +#if UNITY_2021_1_OR_NEWER + if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Secondary) + return false; +#elif UNITY_2020_2_OR_NEWER + if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Slave) + return false; +#elif UNITY_2020_1_OR_NEWER + if (global::Unity.MPE.ProcessService.level == global::Unity.MPE.ProcessLevel.UMP_SLAVE) + return false; +#endif + + return true; + } + } + public static Version LatestLanguageVersionSupported(Assembly assembly) { #if UNITY_2020_2_OR_NEWER diff --git a/Editor/UsageUtility.cs b/Editor/UsageUtility.cs index 9928524..b963d50 100644 --- a/Editor/UsageUtility.cs +++ b/Editor/UsageUtility.cs @@ -64,7 +64,7 @@ namespace Microsoft.Unity.VisualStudio.Editor var scene = SceneManager.GetSceneByPath(scenePath.Replace(Path.DirectorySeparatorChar, '/')); if (!scene.isLoaded) { - var result = UnityEditor.EditorUtility.DisplayDialogComplex("Show Usage", + var result = EditorUtility.DisplayDialogComplex("Show Usage", $"Do you want to open \"{Path.GetFileName(scenePath)}\"?", "Open Scene", "Cancel", diff --git a/Editor/VisualStudioEditor.cs b/Editor/VisualStudioEditor.cs index 60f5410..20e3d3b 100644 --- a/Editor/VisualStudioEditor.cs +++ b/Editor/VisualStudioEditor.cs @@ -35,6 +35,9 @@ namespace Microsoft.Unity.VisualStudio.Editor static VisualStudioEditor() { + if (!UnityInstallation.IsMainUnityEditorProcess) + return; + if (IsWindows) Discovery.FindVSWhere(); @@ -54,11 +57,11 @@ namespace Microsoft.Unity.VisualStudio.Editor catch (Exception ex) { UnityEngine.Debug.LogError($"Error detecting Visual Studio installations: {ex}"); - return Array.Empty(); + return Array.Empty(); } } - internal static bool IsEnabled => CodeEditor.CurrentEditor is VisualStudioEditor; + internal static bool IsEnabled => CodeEditor.CurrentEditor is VisualStudioEditor && UnityInstallation.IsMainUnityEditorProcess; public void CreateIfDoesntExist() { @@ -243,7 +246,6 @@ namespace Microsoft.Unity.VisualStudio.Editor return "Registry packages"; case ProjectGenerationFlag.Unknown: return "Packages from unknown sources"; - case ProjectGenerationFlag.None: default: return string.Empty; } @@ -286,9 +288,7 @@ namespace Microsoft.Unity.VisualStudio.Editor private bool OpenWindowsApp(string path, int line) { - var progpath = FileUtility - .FindPackageAssetFullPath("COMIntegration a:packages", "COMIntegration.exe") - .FirstOrDefault(); + var progpath = FileUtility.GetPackageAssetFullPath("Editor", "COMIntegration", "Release", "COMIntegration.exe"); if (string.IsNullOrWhiteSpace(progpath)) return false; diff --git a/Editor/VisualStudioInstallation.cs b/Editor/VisualStudioInstallation.cs index e1602c7..07c3e70 100644 --- a/Editor/VisualStudioInstallation.cs +++ b/Editor/VisualStudioInstallation.cs @@ -158,6 +158,10 @@ namespace Microsoft.Unity.VisualStudio.Editor var addinPath = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}"); if (File.Exists(IOPath.Combine(addinPath, addinAssembly))) return addinPath; + + addinPath = IOPath.Combine(Path, $"Contents/MonoBundle/Addins/{addinName}"); + if (File.Exists(IOPath.Combine(addinPath, addinAssembly))) + return addinPath; } return null; diff --git a/Editor/VisualStudioIntegration.cs b/Editor/VisualStudioIntegration.cs index 50fbaf5..5683170 100644 --- a/Editor/VisualStudioIntegration.cs +++ b/Editor/VisualStudioIntegration.cs @@ -240,10 +240,9 @@ namespace Microsoft.Unity.VisualStudio.Editor private static void CheckClient(Message message) { - Client client; var endPoint = message.Origin; - if (!_clients.TryGetValue(endPoint, out client)) + if (!_clients.TryGetValue(endPoint, out var client)) { client = new Client { diff --git a/ValidationConfig.json b/ValidationConfig.json new file mode 100644 index 0000000..6a638b8 --- /dev/null +++ b/ValidationConfig.json @@ -0,0 +1,19 @@ +{ + "FileContentKeywordValidation": + { + "Keywords": + [ + { + "Targets": "+", + "Files": + [ + "Editor/Discovery.cs" + ], + "Patterns": + [ + "vswhere\\.exe" + ] + } + ] + } +} \ No newline at end of file diff --git a/ValidationConfig.json.meta b/ValidationConfig.json.meta new file mode 100644 index 0000000..5f57eee --- /dev/null +++ b/ValidationConfig.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a3f71b076c36a204a864f8c44142b3a9 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ValidationExceptions.json b/ValidationExceptions.json new file mode 100644 index 0000000..4db0381 --- /dev/null +++ b/ValidationExceptions.json @@ -0,0 +1,10 @@ +{ + "ErrorExceptions": [ + { + "ValidationTest": "API Validation", + "ExceptionMessage": "Failed comparing against assemblies of previously promoted version of package. \nThis is most likely because the assemblies that were compared against were built with a different version of Unity. \nIf you are certain that there are no API changes warranting bumping the package version then you can add an exception for this error:\nRead more about this error and potential solutions at https://docs.unity3d.com/Packages/com.unity.package-validation-suite@latest/index.html?preview=1&subfolder=/manual/validation_exceptions.html#", + "PackageVersion": "2.0.11" + } + ], + "WarningExceptions": [] +} \ No newline at end of file diff --git a/ValidationExceptions.json.meta b/ValidationExceptions.json.meta new file mode 100644 index 0000000..b0b2427 --- /dev/null +++ b/ValidationExceptions.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 788fbcfc0abc6c54b9504cfee3176c22 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 60df51b..792e140 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,21 @@ "name": "com.unity.ide.visualstudio", "displayName": "Visual Studio Editor", "description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", - "version": "2.0.9", + "version": "2.0.11", "unity": "2019.4", - "unityRelease": "21f1", + "unityRelease": "25f1", "dependencies": { "com.unity.test-framework": "1.1.9" }, "relatedPackages": { - "com.unity.ide.visualstudio.tests": "2.0.9" + "com.unity.ide.visualstudio.tests": "2.0.11" }, "upmCi": { - "footprint": "b1cf463b7fca9fa8a65053f6120bce69640f5cea" + "footprint": "321c90e37eb1ab86fe68d5f7e838c7e4553fc37e" }, "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git", "type": "git", - "revision": "c7cd4c6319806423b3d918b3e9599244e3993d41" + "revision": "680e06d94ffca728223ae2cb14bb919cd354e2c1" } }