diff --git a/Assets/MixedRealityToolkit.Examples/MRTK.Examples.sentinel b/Assets/MixedRealityToolkit.Examples/MRTK.Examples.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Examples/MRTK.Examples.sentinel.meta b/Assets/MixedRealityToolkit.Examples/MRTK.Examples.sentinel.meta new file mode 100644 index 00000000000..5bca428cb8d --- /dev/null +++ b/Assets/MixedRealityToolkit.Examples/MRTK.Examples.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 82b2eec91c3226a46a21e783fc3c885d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.Extensions/MRTK.Extensions.sentinel b/Assets/MixedRealityToolkit.Extensions/MRTK.Extensions.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Extensions/MRTK.Extensions.sentinel.meta b/Assets/MixedRealityToolkit.Extensions/MRTK.Extensions.sentinel.meta new file mode 100644 index 00000000000..a89b3c0d7a1 --- /dev/null +++ b/Assets/MixedRealityToolkit.Extensions/MRTK.Extensions.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 354da3282d4f2d3449f6145955984ed5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.Providers/MRTK.Providers.sentinel b/Assets/MixedRealityToolkit.Providers/MRTK.Providers.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Providers/MRTK.Providers.sentinel.meta b/Assets/MixedRealityToolkit.Providers/MRTK.Providers.sentinel.meta new file mode 100644 index 00000000000..a0f437b1580 --- /dev/null +++ b/Assets/MixedRealityToolkit.Providers/MRTK.Providers.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: efc47659760747d4d80477aa049ee71e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.SDK/MRTK.SDK.sentinel b/Assets/MixedRealityToolkit.SDK/MRTK.SDK.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.SDK/MRTK.SDK.sentinel.meta b/Assets/MixedRealityToolkit.SDK/MRTK.SDK.sentinel.meta new file mode 100644 index 00000000000..78dbdbb9928 --- /dev/null +++ b/Assets/MixedRealityToolkit.SDK/MRTK.SDK.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 864352254c16b15409725d9eb8efcbda +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.Services/MRTK.Services.sentinel b/Assets/MixedRealityToolkit.Services/MRTK.Services.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Services/MRTK.Services.sentinel.meta b/Assets/MixedRealityToolkit.Services/MRTK.Services.sentinel.meta new file mode 100644 index 00000000000..5ba978edffe --- /dev/null +++ b/Assets/MixedRealityToolkit.Services/MRTK.Services.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e4a2c4cac1889464cb3f300e513d9df8 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.Tests/EditModeTests/Core/MixedRealityToolkitFilesTests.cs b/Assets/MixedRealityToolkit.Tests/EditModeTests/Core/MixedRealityToolkitFilesTests.cs index 4ce6337a528..3ce38a0c2b4 100644 --- a/Assets/MixedRealityToolkit.Tests/EditModeTests/Core/MixedRealityToolkitFilesTests.cs +++ b/Assets/MixedRealityToolkit.Tests/EditModeTests/Core/MixedRealityToolkitFilesTests.cs @@ -6,124 +6,44 @@ using UnityEngine; using System; using System.IO; +using System.Linq; namespace Microsoft.MixedReality.Toolkit.Tests.Core { // Tests for the MixedRealityToolkitFiles utility class public class MixedRealityToolkitFilesTests { - string[] basePaths = new string[] { "", "C:\\", "C:\\xyz\\", "C:/xyz/" }; - [Test] - public void FindMatchingModule() + public void TestGetDirectories() { - TestInvalidPath(""); - - // Test invalid base name - TestInvalidPath("aaa"); - TestInvalidPath(".SDK"); - TestInvalidPath("aaa.SDK"); - - // Test missing chars - TestInvalidPath("MixedRealityToolki.SDK"); - TestInvalidPath("MixedRealityToolkit.SD"); - TestInvalidPath("ixedRealityToolkit.SDK"); - TestInvalidPath("MixedRealityToolkit.DK"); - - // Test missing dots - TestInvalidPath("SDK"); - TestInvalidPath("MixedRealityToolkitSDK"); - - // Test valid paths - TestValidPath("MixedRealityToolkit", MixedRealityToolkitModuleType.Core); - TestValidPath("MixedRealityToolkit.SDK", MixedRealityToolkitModuleType.SDK); - - // Test that all modules can be found and internal module map is complete. - // Check that MixedRealityToolkitFiles.moduleNameMap has all entries if this fails! - var modules = Enum.GetValues(typeof(MixedRealityToolkitModuleType)); - var moduleNames = Enum.GetNames(typeof(MixedRealityToolkitModuleType)); - for (int i = 0; i < modules.Length; ++i) + MixedRealityToolkitModuleType[] moduleTypes = new MixedRealityToolkitModuleType[] { - var module = (MixedRealityToolkitModuleType)modules.GetValue(i); - if (module != MixedRealityToolkitModuleType.Core) - { - TestValidPath($"MixedRealityToolkit.{moduleNames[i]}", module); - } - } - } - - [Test] - public void FindMatchingModuleNuget() - { - // Test invalid base name - TestInvalidPath("aaa.1.23-45/MRTK"); - TestInvalidPath(".1.23-45.SDK/MRTK"); - TestInvalidPath("aaa.1.23-45.SDK/MRTK"); - - // Test missing chars - TestInvalidPath("Microsoft.MixedReality.Toolki.SDK.1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.Toolkit.SD.1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.Toolkit.SDK.1.23-45/MRT"); - - TestInvalidPath("icrosoft.MixedReality.Toolkit.SDK.1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.Toolkit.DK.1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.Toolkit.SDK.1.23-45/RTK"); - - // Test missing dots - TestInvalidPath("Microsoft.MixedReality.Toolkit.SDK1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.ToolkitSDK.1.23-45/MRTK"); - TestInvalidPath("Microsoft.MixedReality.ToolkitSDK1.23-45/MRTK"); - - // Test missing version - TestInvalidPath("Microsoft.MixedReality.Toolkit.SDK/MRTK"); - // Test missing MRTK suffix - TestInvalidPath("Microsoft.MixedReality.Toolkit.SDK.1.23-45"); - - // Test valid paths - TestValidPath("Microsoft.MixedReality.Toolkit.1.23-45/MRTK", MixedRealityToolkitModuleType.Core); - TestValidPath("Microsoft.MixedReality.Toolkit.SDK.1.23-45/MRTK", MixedRealityToolkitModuleType.SDK); - - // Test that all modules can be found and internal module map is complete. - // Check that MixedRealityToolkitFiles.moduleNameMap has all entries if this fails! - var modules = Enum.GetValues(typeof(MixedRealityToolkitModuleType)); - var moduleNames = Enum.GetNames(typeof(MixedRealityToolkitModuleType)); - for (int i = 0; i < modules.Length; ++i) + MixedRealityToolkitModuleType.Core, + MixedRealityToolkitModuleType.Providers, + MixedRealityToolkitModuleType.Services, + MixedRealityToolkitModuleType.SDK, + MixedRealityToolkitModuleType.Examples, + MixedRealityToolkitModuleType.Tests, + MixedRealityToolkitModuleType.Extensions, + MixedRealityToolkitModuleType.Tools, + }; + + MixedRealityToolkitFiles.RefreshFolders(); + foreach (var moduleType in moduleTypes) { - var module = (MixedRealityToolkitModuleType)modules.GetValue(i); - if (module != MixedRealityToolkitModuleType.Core) - { - TestValidPath($"Microsoft.MixedReality.Toolkit.{moduleNames[i]}.1.23-45/MRTK", (MixedRealityToolkitModuleType)modules.GetValue(i)); - } + // Validate that each module has a corresponding found folder + Assert.IsTrue(MixedRealityToolkitFiles.GetDirectories(moduleType).Any()); } } /// /// Validates that MixedRealityToolkitFiles is able to reason over MRTK folders when placed in the root Asset directory. /// + [Test] public void TestRootAssetFolderResolution() { - string resolvedPath = MixedRealityToolkitFiles.MapRelativeFilePathToAbsolutePath("Inspectors/Data/EditorWindowOptions.json"); - string expectedPath = Path.Combine(Application.dataPath, "MixedRealityToolkit/Inspectors/Data/EditorWindowOptions.json"); - Assert.AreEqual(expectedPath, resolvedPath); - } - - public void TestInvalidPath(string path) - { - foreach (string basePath in basePaths) - { - string fullPath = Path.Combine(basePath, path); - Assert.False(MixedRealityToolkitFiles.FindMatchingModule(fullPath, out MixedRealityToolkitModuleType module)); - } - } - - public void TestValidPath(string path, MixedRealityToolkitModuleType expectedModule) - { - foreach (string basePath in basePaths) - { - string fullPath = Path.Combine(basePath, path); - Assert.True(MixedRealityToolkitFiles.FindMatchingModule(fullPath, out MixedRealityToolkitModuleType module)); - Assert.AreEqual(module, expectedModule); - } + string resolvedPath = MixedRealityToolkitFiles.MapRelativeFilePathToAbsolutePath("Inspectors\\Data\\EditorWindowOptions.json"); + Assert.IsNotNull(resolvedPath); } [TearDown] diff --git a/Assets/MixedRealityToolkit.Tests/MRTK.Tests.sentinel b/Assets/MixedRealityToolkit.Tests/MRTK.Tests.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Tests/MRTK.Tests.sentinel.meta b/Assets/MixedRealityToolkit.Tests/MRTK.Tests.sentinel.meta new file mode 100644 index 00000000000..9c5d29ff9dd --- /dev/null +++ b/Assets/MixedRealityToolkit.Tests/MRTK.Tests.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 602691336a9ce9749a76d54221dcc85b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit.Tools/MRTK.Tools.sentinel b/Assets/MixedRealityToolkit.Tools/MRTK.Tools.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit.Tools/MRTK.Tools.sentinel.meta b/Assets/MixedRealityToolkit.Tools/MRTK.Tools.sentinel.meta new file mode 100644 index 00000000000..abb09afa2d9 --- /dev/null +++ b/Assets/MixedRealityToolkit.Tools/MRTK.Tools.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e5bf78b8638c3604b88d3e0b880941e2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit/MRTK.Core.sentinel b/Assets/MixedRealityToolkit/MRTK.Core.sentinel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Assets/MixedRealityToolkit/MRTK.Core.sentinel.meta b/Assets/MixedRealityToolkit/MRTK.Core.sentinel.meta new file mode 100644 index 00000000000..a2e05c3b219 --- /dev/null +++ b/Assets/MixedRealityToolkit/MRTK.Core.sentinel.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 05ba0ec971b0d5648a404e6ac3ae4cab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MixedRealityToolkit/Utilities/Editor/Setup/MixedRealityToolkitFiles.cs b/Assets/MixedRealityToolkit/Utilities/Editor/Setup/MixedRealityToolkitFiles.cs index 776988ccc8a..8c7db0b23ca 100644 --- a/Assets/MixedRealityToolkit/Utilities/Editor/Setup/MixedRealityToolkitFiles.cs +++ b/Assets/MixedRealityToolkit/Utilities/Editor/Setup/MixedRealityToolkitFiles.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using UnityEditor; using UnityEngine; @@ -16,6 +17,7 @@ namespace Microsoft.MixedReality.Toolkit.Utilities.Editor /// public enum MixedRealityToolkitModuleType { + None = 0, Core, Generated, Providers, @@ -34,6 +36,15 @@ public enum MixedRealityToolkitModuleType /// /// API for working with MixedRealityToolkit folders contained in the project. /// + /// + /// This class works by looking for sentinel files (following the pattern MRTK.*.sentinel, + /// for example, MRTK.Core.sentinel) in order to identify where the MRTK is located + /// within the project. + /// + /// If the MRTK is being consumed as code that sits within the Assets folder, the "root" + /// MRTK folder must be at most three directories deep - this search code will only reason + /// over MRTK folders that sit in a depth range [0, 3]. + /// [InitializeOnLoad] public static class MixedRealityToolkitFiles { @@ -49,6 +60,14 @@ private enum SearchType Folder, } + /// + /// The MRTK uses "sentinel" files (for example, MRTK.Core.sentinel) which are used to uniquely + /// identify the presence of certain MRTK folders and modules. This is the file pattern used + /// to search within folders for those sentinel files and make the file search a little more + /// efficient than a full file enumeration. + /// + private const string SentinelFilePattern = "MRTK.*.sentinel"; + /// /// In order to subscribe for a callback, /// the class declaring the method must derive from AssetPostprocessor. So this class is nested privately as to prevent instantiation of it. @@ -61,13 +80,14 @@ public static void OnPostprocessAllAssets(string[] importedAssets, string[] dele foreach (string asset in importedAssets.Concat(movedAssets)) { - string folder = ResolveFullAssetsPath(asset); - TryRegisterModuleFolder(folder); + string fullAssetPath = ResolveFullAssetsPath(asset); + TryRegisterModuleFile(fullAssetPath); } foreach (string asset in deletedAssets.Concat(movedFromAssetPaths)) { - string folder = ResolveFullAssetsPath(asset); + string fullAssetPath = ResolveFullAssetsPath(asset); + string folder = Path.GetDirectoryName(fullAssetPath); TryUnregisterModuleFolder(folder); } } @@ -159,26 +179,38 @@ private static void SearchForFoldersAsync(string rootPath) } } - private static bool TryRegisterModuleFolder(string folder) + private static void TryRegisterModuleFolder(string folder) { - return TryRegisterModuleFolder(folder, out MixedRealityToolkitModuleType module); + string normalizedFolder = NormalizeSeparators(folder); + List modules; + if (FindMatchingModule(normalizedFolder, out modules)) + { + foreach (var module in modules) + { + if (!mrtkFolders.TryGetValue(module, out HashSet modFolders)) + { + modFolders = new HashSet(); + mrtkFolders.Add(module, modFolders); + } + modFolders.Add(normalizedFolder); + } + } } - private static bool TryRegisterModuleFolder(string folder, out MixedRealityToolkitModuleType module) + private static void TryRegisterModuleFile(string fullAssetPath) { - string normalizedFolder = NormalizeSeparators(folder); - if (FindMatchingModule(normalizedFolder, out module)) + MixedRealityToolkitModuleType moduleType = MatchModuleType(fullAssetPath); + if (moduleType != MixedRealityToolkitModuleType.None) { - if (!mrtkFolders.TryGetValue(module, out HashSet modFolders)) + if (!mrtkFolders.TryGetValue(moduleType, out HashSet modFolders)) { modFolders = new HashSet(); - mrtkFolders.Add(module, modFolders); + mrtkFolders.Add(moduleType, modFolders); } - modFolders.Add(normalizedFolder); - return true; - } - return false; + string folder = Path.GetDirectoryName(fullAssetPath); + modFolders.Add(NormalizeSeparators(folder)); + } } private static bool TryUnregisterModuleFolder(string folder) @@ -333,7 +365,7 @@ private static string MapRelativePathToAbsolutePath(SearchType searchType, Mixed private static readonly Dictionary moduleNameMap = new Dictionary() { - { "", MixedRealityToolkitModuleType.Core }, + { "Core", MixedRealityToolkitModuleType.Core }, { "Generated", MixedRealityToolkitModuleType.Generated }, { "Providers", MixedRealityToolkitModuleType.Providers }, { "Services", MixedRealityToolkitModuleType.Services }, @@ -349,54 +381,59 @@ private static string MapRelativePathToAbsolutePath(SearchType searchType, Mixed { "AdhocTesting", MixedRealityToolkitModuleType.AdhocTesting }, }; - public static bool FindMatchingModule(string path, out MixedRealityToolkitModuleType result) + /// + /// Try to find the matching modules associated with path by looking for the presence of + /// MRTK sentinel files. + /// + /// + /// In certain consumption situations, the content can be placed within the same folder, + /// meaning it's possible for multiple sentinel values to exist within the same folder. + /// + public static bool FindMatchingModule(string path, out List result) { - // Matches an optional module suffix, e.g. ".Services" - const string modulePattern = @"(\.(?[a-zA-Z]+))?"; - // Matches a version string, e.g. "2.0.0-20190611.2" - const string versionPattern = @"(?[.\-0-9]+)"; - // Matches the naming pattern in the MRTK repository - // e.g. "MixedRealityToolkit.Services" - const string mrtkPattern = @"^MixedRealityToolkit" + modulePattern + @"$"; - // Matches "Microsoft.MixedReality.Toolkit", followed by optional module name, followed by version number - // e.g.: "Microsoft.MixedReality.Toolkit.Services.2.0.0-20190611.2" - // This alternate path is used if above isn't found. This is to work around long paths issue with NuGetForUnity - // https://github.com/GlitchEnzo/NuGetForUnity/issues/246 - const string nugetParentPattern = @"^Microsoft\.MixedReality\.Toolkit" + modulePattern + @"\." + versionPattern + @"$"; - + result = null; + if (path.Length > 0) { - var dirInfo = new DirectoryInfo(path); - if (TryMatchFolderPattern(dirInfo.Name, mrtkPattern, out result)) + // Note that path can be a file or a directory + DirectoryInfo directoryInfo = new DirectoryInfo(path); + var sentinelFiles = Directory.EnumerateFiles(directoryInfo.FullName, SentinelFilePattern); + foreach (var sentinelFile in sentinelFiles) { - return true; - } - else if (dirInfo.Name == "MRTK" - && dirInfo.Parent != null - && TryMatchFolderPattern(dirInfo.Parent.Name, nugetParentPattern, out result)) - { - return true; + if (result == null) + { + result = new List(); + } + MixedRealityToolkitModuleType moduleType = MatchModuleType(sentinelFile); + if (moduleType != MixedRealityToolkitModuleType.None) + { + result.Add(moduleType); + } } } - result = MixedRealityToolkitModuleType.Core; - return false; + return result != null; } - private static bool TryMatchFolderPattern(string name, string pattern, out MixedRealityToolkitModuleType result) + /// + /// Given the full file path, returns the module it's associated with (if it is an MRTK + /// sentinel file). + /// + private static MixedRealityToolkitModuleType MatchModuleType(string filePath) { - var folderMatches = System.Text.RegularExpressions.Regex.Matches(name, pattern); - if (folderMatches.Count == 1) + const string sentinelRegexPattern = @"^MRTK\.(?[a-zA-Z]+)\.sentinel"; + string fileName = Path.GetFileName(filePath); + var matches = Regex.Matches(fileName, sentinelRegexPattern); + if (matches.Count == 1) { - var moduleName = folderMatches[0].Groups["module"].Value; - if (moduleNameMap.TryGetValue(moduleName, out result)) + var moduleName = matches[0].Groups["module"].Value; + MixedRealityToolkitModuleType moduleType; + if (moduleNameMap.TryGetValue(moduleName, out moduleType)) { - return true; + return moduleType; } } - - result = MixedRealityToolkitModuleType.Core; - return false; + return MixedRealityToolkitModuleType.None; } ///