using System; using System.Collections.Generic; using System.IO; using System.Linq; using Unity.XR.CoreUtils.Editor; using UnityEngine; namespace UnityEditor.XR.Interaction.Toolkit.ProjectValidation { /// /// Utility class to help with project validation for XR Interaction Toolkit and Samples. /// internal static class ProjectValidationUtility { const string k_SamplesRootDirectoryName = "Samples"; /// /// This is the minimum version of the Starter Assets sample for the XR Interaction Toolkit that enforces correct behavior /// of newly added properties, prefabs, or input actions. /// public static readonly PackageVersion minimumXRIStarterAssetsSampleVersion = new PackageVersion("3.1.0"); /// /// Dictionary used to cache packages imported samples. The dictionary key is the package display name as displayed in package Samples directory. /// static Dictionary s_PackageSampleCache; struct PackageSampleData { public string packageDisplayName; public Dictionary importedSamples; } /// /// Struct containing data about a sample. /// struct SampleData { /// /// The display name of the sample, matches the directory. /// public string sampleName; /// /// The display name of the package the sample is from. /// public string packageDisplayName; /// /// The version of the package the sample is imported from. /// public PackageVersion packageVersion; } /// /// Searches for a sample, from a specified package, . /// The search is done by iterating through the Samples directory, but will utilize cached data if available. /// This function is version agnostic and will return true if the sample is found in any version of the package's /// sample directory. /// /// The name of the package directory that contains the sample. /// The name of the sample directory to search for. /// Returns if the sample is found in the Samples directory of the specified /// package. Otherwise, returns . /// public static bool HasSampleImported(string packageDisplayName, string sampleDisplayName) { if (s_PackageSampleCache == null) { UpdatePackageSampleCache(); if (s_PackageSampleCache == null) return false; } return s_PackageSampleCache.TryGetValue(packageDisplayName, out var sampleData) && sampleData.importedSamples.ContainsKey(sampleDisplayName); } /// /// Searches for the imported samples from a specified package. /// /// The display name of the package (directory that contains the samples). /// The list to populate with the unordered collection of imported samples. public static void GetImportedSamples(string packageDisplayName, List<(string sampleName, PackageVersion packageVersion)> results) { results.Clear(); if (s_PackageSampleCache == null) { UpdatePackageSampleCache(); if (s_PackageSampleCache == null) return; } if (s_PackageSampleCache.TryGetValue(packageDisplayName, out var sampleData)) { foreach (var importedSample in sampleData.importedSamples) { results.Add((importedSample.Value.sampleName, importedSample.Value.packageVersion)); } } } /// /// Searches for a sample, from a specified package, /// and compares the sample version. /// /// The name of the package directory that contains the sample. /// The name of the sample directory to search for. /// The minimum package version the sample should be imported from. /// Returns if the sample is found in the Samples directory of the specified /// package and the imported sample's version is greater than or equal to the . public static bool SampleImportMeetsMinimumVersion(string packageDisplayName, string sampleDisplayName, PackageVersion minVersion) { if (HasSampleImported(packageDisplayName, sampleDisplayName)) return s_PackageSampleCache[packageDisplayName].importedSamples[sampleDisplayName].packageVersion >= minVersion; return false; } /// /// Iterates through Samples directory and caches sample and package data. /// static void UpdatePackageSampleCache() { if (s_PackageSampleCache == null) { try { s_PackageSampleCache = new Dictionary(); var delimiter = Path.DirectorySeparatorChar; var sampleRootPath = Path.Combine(Path.GetFileName(Application.dataPath), k_SamplesRootDirectoryName); if (!Directory.Exists(sampleRootPath)) { Debug.LogWarning($"Could not find Samples directory ({sampleRootPath}). Failed to update package sample cache."); return; } // Iterate through all package directories in Samples directory var allSamplePackagesPaths = Directory.GetDirectories(sampleRootPath); foreach (var packageDirectoryPath in allSamplePackagesPaths) { var packageDisplayName = packageDirectoryPath.Split(delimiter).Last(); // To contain all samples for this package var sampleMap = new Dictionary(); // Iterate through all version directories in package var versionDirectoryPaths = Directory.GetDirectories(packageDirectoryPath); foreach (var versionDirectoryPath in versionDirectoryPaths) { var versionName = versionDirectoryPath.Split(delimiter).Last(); var packageVersion = new PackageVersion(versionName); // Iterate through all sample directories in version var samplesDirectoryPaths = Directory.GetDirectories(versionDirectoryPath); foreach (var sampleDirectoryPath in samplesDirectoryPaths) { var sampleName = sampleDirectoryPath.Split(delimiter).Last(); sampleMap.Add(sampleName, new SampleData { sampleName = sampleName, packageDisplayName = packageDisplayName, packageVersion = packageVersion, }); } } var packageCacheData = new PackageSampleData { packageDisplayName = packageDisplayName, importedSamples = sampleMap, }; s_PackageSampleCache[packageDisplayName] = packageCacheData; } } catch (Exception e) { Debug.LogError("Failed to update package sample cache. " + e.Message); } } } } }