Skip to content

Instantly share code, notes, and snippets.

@bzgeb
Last active September 3, 2022 17:53
Show Gist options
  • Save bzgeb/4003226a0d9af855c7ff20750e9b2490 to your computer and use it in GitHub Desktop.
Save bzgeb/4003226a0d9af855c7ff20750e9b2490 to your computer and use it in GitHub Desktop.

Revisions

  1. bzgeb revised this gist Apr 27, 2022. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions StarterAssetsPackageChecker.cs
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,6 @@
    // This file is licensed under the Unity Companion License.
    // For full license terms, please see: https://unity3d.com/legal/licenses/Unity_Companion_License

    using System;
    using System.Collections.Generic;
    using System.IO;
  2. bzgeb created this gist Apr 27, 2022.
    312 changes: 312 additions & 0 deletions StarterAssetsPackageChecker.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,312 @@
    using System;
    using System.Collections.Generic;
    using System.IO;
    using UnityEditor;
    using UnityEditor.PackageManager;
    using UnityEditor.PackageManager.Requests;
    using UnityEngine;
    using PackageInfo = UnityEditor.PackageManager.PackageInfo;

    namespace StarterAssetsPackageChecker
    {
    public static class PackageChecker
    {
    class PackageEntry
    {
    public string Name;
    public string Version;
    }

    [Serializable]
    class Settings
    {
    public string EditorFolderRoot = "Assets/StarterAssets/";

    public string[] PackagesToAdd = new string[]
    {
    "com.unity.cinemachine",
    "com.unity.inputsystem"
    };

    public string PackageCheckerScriptingDefine => "STARTER_ASSETS_PACKAGES_CHECKED";
    }

    static ListRequest _clientList;
    static SearchRequest _compatibleList;
    static List<PackageEntry> _packagesToAdd;

    static AddRequest[] _addRequests;
    static bool[] _installRequired;

    static Settings _settings;

    [InitializeOnLoadMethod]
    static void CheckPackage()
    {
    _settings = new Settings();
    var settingsFiles = Directory.GetFiles(Application.dataPath, "PackageCheckerSettings.json",
    SearchOption.AllDirectories);
    if (settingsFiles.Length > 0)
    {
    JsonUtility.FromJsonOverwrite(File.ReadAllText(settingsFiles[0]), _settings);
    }

    // if we dont have the scripting define, it means the check has not been done
    if (!CheckScriptingDefine(_settings.PackageCheckerScriptingDefine))
    {
    _packagesToAdd = new List<PackageEntry>();
    _clientList = null;
    _compatibleList = null;

    _packagesToAdd = new List<PackageEntry>();

    foreach (string line in _settings.PackagesToAdd)
    {
    string[] split = line.Split('@');

    // if no version is given, return null
    PackageEntry entry = new PackageEntry
    { Name = split[0], Version = split.Length > 1 ? split[1] : null };

    _packagesToAdd.Add(entry);
    }

    // Create a file in library that is queried to see if CheckPackage() has been run already
    SetScriptingDefine(_settings.PackageCheckerScriptingDefine);

    // create a list of compatible packages for current engine version
    _compatibleList = Client.SearchAll();

    while (!_compatibleList.IsCompleted)
    {
    if (_compatibleList.Status == StatusCode.Failure || _compatibleList.Error != null)
    {
    if (_compatibleList.Error != null)
    {
    Debug.LogError(_compatibleList.Error.message);
    break;
    }
    }
    }

    // create a list of packages found in the engine
    _clientList = Client.List();

    while (!_clientList.IsCompleted)
    {
    if (_clientList.Status == StatusCode.Failure || _clientList.Error != null)
    {
    if (_clientList.Error != null)
    {
    Debug.LogError(_clientList.Error.message);
    break;
    }
    }
    }

    _addRequests = new AddRequest[_packagesToAdd.Count];
    _installRequired = new bool[_packagesToAdd.Count];

    // default new packages to install = false. we will mark true after validating they're required
    for (int i = 0; i < _installRequired.Length; i++)
    {
    _installRequired[i] = false;
    }

    // build data collections compatible packages for this project, and packages within the project
    List<PackageInfo> compatiblePackages =
    new List<PackageInfo>();
    List<PackageInfo> clientPackages =
    new List<PackageInfo>();

    foreach (var result in _compatibleList.Result)
    {
    compatiblePackages.Add(result);
    }

    foreach (var result in _clientList.Result)
    {
    clientPackages.Add(result);
    }

    // check for the latest verified package version for each package that is missing a version
    for (int i = 0; i < _packagesToAdd.Count; i++)
    {
    // if a version number is not provided
    if (_packagesToAdd[i].Version == null)
    {
    foreach (var package in compatiblePackages)
    {
    // if no latest verified version found, PackageChecker will just install latest release
    if (_packagesToAdd[i].Name == package.name && package.versions.verified != string.Empty)
    {
    // add latest verified version number to the packagetoadd list version
    // so that we get the latest verified version only
    _packagesToAdd[i].Version = package.versions.verified;

    // add to our install list
    _installRequired[i] = true;

    //Debug.Log(string.Format("Requested {0}. Latest verified compatible package found: {1}",
    // packagesToAdd[i].name, packagesToAdd[i].version));
    }
    }
    }

    // we don't need to catch packages that are not installed as their latest version has been collected
    // from the campatiblelist result
    foreach (var package in clientPackages)
    {
    if (_packagesToAdd[i].Name == package.name)
    {
    // see what version we have installed
    switch (CompareVersion(_packagesToAdd[i].Version, package.version))
    {
    // latest verified is ahead of installed version
    case 1:
    _installRequired[i] = EditorUtility.DisplayDialog("Confirm Package Upgrade",
    $"The version of \"{_packagesToAdd[i].Name}\" in this project is {package.version}. The latest verified " +
    $"version is {_packagesToAdd[i].Version}. Would you like to upgrade it to the latest version? (Recommended)",
    "Yes", "No");

    Debug.Log(
    $"<b>Package version behind</b>: {package.packageId} is behind latest verified " +
    $"version {package.versions.verified}. prompting user install");
    break;

    // latest verified matches installed version
    case 0:
    _installRequired[i] = false;

    Debug.Log(
    $"<b>Package version match</b>: {package.packageId} matches latest verified version " +
    $"{package.versions.verified}. Skipped install");
    break;

    // latest verified is behind installed version
    case -1:
    _installRequired[i] = EditorUtility.DisplayDialog("Confirm Package Downgrade",
    $"The version of \"{_packagesToAdd[i].Name}\" in this project is {package.version}. The latest verified version is {_packagesToAdd[i].Version}. " +
    $"{package.version} is unverified. Would you like to downgrade it to the latest verified version? " +
    "(Recommended)", "Yes", "No");

    Debug.Log(
    $"<b>Package version ahead</b>: {package.packageId} is newer than latest verified " +
    $"version {package.versions.verified}, skipped install");
    break;
    }
    }
    }
    }

    // install our packages and versions
    for (int i = 0; i < _packagesToAdd.Count; i++)
    {
    if (_installRequired[i])
    {
    _addRequests[i] = InstallSelectedPackage(_packagesToAdd[i].Name, _packagesToAdd[i].Version);
    }
    }

    ReimportPackagesByKeyword();
    }
    }

    static AddRequest InstallSelectedPackage(string packageName, string packageVersion)
    {
    if (packageVersion != null)
    {
    packageName = packageName + "@" + packageVersion;
    Debug.Log($"<b>Adding package</b>: {packageName}");
    }

    AddRequest newPackage = Client.Add(packageName);

    while (!newPackage.IsCompleted)
    {
    if (newPackage.Status == StatusCode.Failure || newPackage.Error != null)
    {
    if (newPackage.Error != null)
    {
    Debug.LogError(newPackage.Error.message);
    return null;
    }
    }
    }

    return newPackage;
    }

    static void ReimportPackagesByKeyword()
    {
    AssetDatabase.Refresh();
    AssetDatabase.ImportAsset(_settings.EditorFolderRoot, ImportAssetOptions.ImportRecursive);
    }

    public static int CompareVersion(string latestVerifiedVersion, string projectVersion)
    {
    string[] latestVersionSplit = latestVerifiedVersion.Split('.');
    string[] projectVersionSplit = projectVersion.Split('.');
    int iteratorA = 0;
    int iteratorB = 0;

    while (iteratorA < latestVersionSplit.Length || iteratorB < projectVersionSplit.Length)
    {
    int latestVerified = 0;
    int installed = 0;

    if (iteratorA < latestVersionSplit.Length)
    {
    latestVerified = Convert.ToInt32(latestVersionSplit[iteratorA]);
    }

    if (iteratorB < projectVersionSplit.Length)
    {
    installed = Convert.ToInt32(projectVersionSplit[iteratorB]);
    }

    // latest verified is ahead of installed version
    if (latestVerified > installed) return 1;

    // latest verified is behind installed version
    if (latestVerified < installed) return -1;

    iteratorA++;
    iteratorB++;
    }

    // if the version is the same
    return 0;
    }


    static bool CheckScriptingDefine(string scriptingDefine)
    {
    BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
    var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
    return defines.Contains(scriptingDefine);
    }

    static void SetScriptingDefine(string scriptingDefine)
    {
    BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
    var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
    if (!defines.Contains(scriptingDefine))
    {
    defines += $";{scriptingDefine}";
    PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines);
    }
    }

    public static void RemovePackageCheckerScriptingDefine()
    {
    BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
    var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
    if (defines.Contains(_settings.PackageCheckerScriptingDefine))
    {
    string newDefines = defines.Replace(_settings.PackageCheckerScriptingDefine, "");
    PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, newDefines);
    }
    }
    }
    }