using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.XR.CoreUtils.Editor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEditor.PackageManager.UI;
using UnityEditor.XR.Interaction.Toolkit.ProjectValidation;
using UnityEngine;
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
using TMPro;
#endif
namespace UnityEditor.XR.Interaction.Toolkit.Samples.ARStarterAssets.Editor
{
///
/// Unity Editor class which registers Project Validation rules for the AR Starter Assets sample,
/// checking that other required samples are installed.
///
static class ARStarterAssetsSampleProjectValidation
{
const string k_SampleDisplayName = "AR Starter Assets";
const string k_Category = "XR Interaction Toolkit";
const string k_StarterAssetsSampleName = "Starter Assets";
const string k_XRIPackageName = "com.unity.xr.interaction.toolkit";
const string k_ARFPackageName = "com.unity.xr.arfoundation";
const string k_ARFPackageMinVersionString = "4.2.8";
const float k_TimeOutInSeconds = 3f;
#if UNITY_6000_0_OR_NEWER
// The s_MinimumUIPackageVersion should match the UGUI_2_0_PRESENT version in the
// Unity.XR.Interaction.Toolkit.Samples.StarterAssets.Editor.asmdef
// and the Unity.XR.Interaction.Toolkit.Samples.StarterAssets.asmdef
static readonly PackageVersion s_MinimumUIPackageVersion = new PackageVersion("2.0.0");
const string k_UIPackageName = "com.unity.ugui";
const string k_UIPackageDisplayName = "Unity UI";
#else
// The s_MinimumUIPackageVersion should match the TEXT_MESH_PRO_PRESENT version in the
// Unity.XR.Interaction.Toolkit.Samples.StarterAssets.Editor.asmdef
// and the Unity.XR.Interaction.Toolkit.Samples.StarterAssets.asmdef
static readonly PackageVersion s_MinimumUIPackageVersion = new PackageVersion("3.0.8");
const string k_UIPackageName = "com.unity.textmeshpro";
const string k_UIPackageDisplayName = "TextMeshPro";
#endif
static AddRequest s_UIPackageAddRequest;
static readonly PackageVersion s_ARFPackageMinVersion = new PackageVersion(k_ARFPackageMinVersionString);
static readonly BuildTargetGroup[] s_BuildTargetGroups =
((BuildTargetGroup[])Enum.GetValues(typeof(BuildTargetGroup))).Distinct().ToArray();
static readonly List s_BuildValidationRules = new List
{
new BuildValidationRule
{
IsRuleEnabled = () => s_ARFPackageAddRequest == null || s_ARFPackageAddRequest.IsCompleted,
Message = $"[{k_SampleDisplayName}] AR Foundation ({k_ARFPackageName}) package must be installed or updated to use this sample.",
Category = k_Category,
CheckPredicate = () => PackageVersionUtility.GetPackageVersion(k_ARFPackageName) >= s_ARFPackageMinVersion,
FixIt = () =>
{
var packString = k_ARFPackageName;
var searchResult = Client.Search(k_ARFPackageName, true);
var timeout = Time.realtimeSinceStartup + k_TimeOutInSeconds;
while (!searchResult.IsCompleted && timeout > Time.realtimeSinceStartup)
{
System.Threading.Thread.Sleep(10);
}
if (searchResult.IsCompleted)
{
var version = searchResult.Result
.Where((info) => string.Compare(k_ARFPackageName, info.name) == 0)
.Select(info => info.versions.recommended)
.FirstOrDefault();
if (!string.IsNullOrEmpty(version))
{
var verifiedVersion = new PackageVersion(version);
if (verifiedVersion >= s_ARFPackageMinVersion)
{
packString = k_ARFPackageName + "@" + version;
}
else
{
Debug.LogError($"Package installation error: {k_ARFPackageMinVersionString}@{version} is below the minimum version of {k_ARFPackageMinVersionString}. Please install manually from Package Manager or update to a newer version of the Unity Editor.");
return;
}
}
}
else
{
Debug.LogWarning($"Timeout trying to get package list after {k_TimeOutInSeconds} seconds.");
}
s_ARFPackageAddRequest = Client.Add(packString);
if (s_ARFPackageAddRequest.Error != null)
{
Debug.LogError($"Package installation error: {s_ARFPackageAddRequest.Error}: {s_ARFPackageAddRequest.Error.message}");
}
},
FixItAutomatic = true,
Error = true,
},
new BuildValidationRule
{
Message = $"[{k_SampleDisplayName}] {k_StarterAssetsSampleName} sample from XR Interaction Toolkit ({k_XRIPackageName}) package must be imported or updated to use this sample. {GetImportSampleVersionMessage(k_Category, k_StarterAssetsSampleName, ProjectValidationUtility.minimumXRIStarterAssetsSampleVersion)}",
Category = k_Category,
CheckPredicate = () => ProjectValidationUtility.SampleImportMeetsMinimumVersion(k_Category, k_StarterAssetsSampleName, ProjectValidationUtility.minimumXRIStarterAssetsSampleVersion),
FixIt = () =>
{
if (TryFindSample(k_XRIPackageName, string.Empty, k_StarterAssetsSampleName, out var sample))
{
sample.Import(Sample.ImportOptions.OverridePreviousImports);
}
},
FixItAutomatic = true,
Error = !ProjectValidationUtility.HasSampleImported(k_Category, k_StarterAssetsSampleName),
},
new BuildValidationRule
{
IsRuleEnabled = () => s_UIPackageAddRequest == null || s_UIPackageAddRequest.IsCompleted,
Message = $"[{k_StarterAssetsSampleName}] {k_UIPackageDisplayName} ({k_UIPackageName}) package must be installed and at minimum version {s_MinimumUIPackageVersion}.",
Category = k_Category,
CheckPredicate = () => PackageVersionUtility.GetPackageVersion(k_UIPackageName) >= s_MinimumUIPackageVersion,
FixIt = () =>
{
if (s_UIPackageAddRequest == null || s_UIPackageAddRequest.IsCompleted)
ProjectValidationUtility.InstallOrUpdatePackage(k_UIPackageName, s_MinimumUIPackageVersion, ref s_UIPackageAddRequest);
},
FixItAutomatic = true,
Error = true,
},
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
new BuildValidationRule
{
IsRuleEnabled = () => PackageVersionUtility.IsPackageInstalled(k_UIPackageName),
Message = $"[{k_SampleDisplayName}] TextMesh Pro - TMP Essentials must be installed for this sample.",
HelpText = "Can be installed using Window > TextMeshPro > Import TMP Essential Resources or by clicking this Edit button and then Import TMP Essentials in the window that appears.",
Category = k_Category,
CheckPredicate = () => PackageVersionUtility.IsPackageInstalled(k_UIPackageName) && TextMeshProEssentialsInstalled(),
FixIt = () =>
{
TMP_PackageResourceImporterWindow.ShowPackageImporterWindow();
},
FixItAutomatic = false,
Error = true,
},
#endif
};
static AddRequest s_ARFPackageAddRequest;
[InitializeOnLoadMethod]
static void RegisterProjectValidationRules()
{
foreach (var buildTargetGroup in s_BuildTargetGroups)
{
BuildValidator.AddRules(buildTargetGroup, s_BuildValidationRules);
}
}
static bool TryFindSample(string packageName, string packageVersion, string sampleDisplayName, out Sample sample)
{
sample = default;
var packageSamples = Sample.FindByPackage(packageName, packageVersion);
if (packageSamples == null)
{
Debug.LogError($"Couldn't find samples of the {ToString(packageName, packageVersion)} package; aborting project validation rule.");
return false;
}
foreach (var packageSample in packageSamples)
{
if (packageSample.displayName == sampleDisplayName)
{
sample = packageSample;
return true;
}
}
Debug.LogError($"Couldn't find {sampleDisplayName} sample in the {ToString(packageName, packageVersion)} package; aborting project validation rule.");
return false;
}
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
static bool TextMeshProEssentialsInstalled()
{
// Matches logic in Project Settings window, see TMP_PackageResourceImporter.cs.
// For simplicity, we don't also copy the check if the asset needs to be updated.
return File.Exists("Assets/TextMesh Pro/Resources/TMP Settings.asset");
}
#endif
static string ToString(string packageName, string packageVersion)
{
return string.IsNullOrEmpty(packageVersion) ? packageName : $"{packageName}@{packageVersion}";
}
static string GetImportSampleVersionMessage(string packageFolderName, string sampleDisplayName, PackageVersion version)
{
if (ProjectValidationUtility.SampleImportMeetsMinimumVersion(packageFolderName, sampleDisplayName, version) || !ProjectValidationUtility.HasSampleImported(packageFolderName, sampleDisplayName))
return string.Empty;
return $"An older version of {sampleDisplayName} has been found. This may cause errors.";
}
}
}