Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Detect if a package is installed?

Discussion in 'Scripting' started by Gillissie, Apr 27, 2021.

  1. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    294
    I've googled this, and there's a couple of hits related to using the PackageManager API to do this. However, when I use that, it shows info about the package regardless of whether it's currently installed or not, and there doesn't seem to be a property on the PackageInfo class to tell me whether it's installed or not. Am I missing something here, or is this really not possible using the API?

    https://stackoverflow.com/questions/59531826/how-can-i-check-if-package-is-installed-in-the-project

    Client.List() is resulting in packages even if they're not installed.

    I'm about to resort to reading "Packages/manifest.json" outside of the API to see if it's really installed.
     
  2. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Very crude example:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Linq;
    3. using UnityEngine;
    4. using UnityEditor.PackageManager;
    5.  
    6. public class Test : MonoBehaviour
    7. {
    8.     private IEnumerator  Start()
    9.     {
    10.         var pack = Client.List();
    11.         while (!pack.IsCompleted) yield return null;
    12.         var haveProgrids = pack.Result.FirstOrDefault(q => q.name == "com.unity.progrids");
    13.         Debug.Log(haveProgrids);
    14.     }
    15. }
    16.  
    haveProgids
    is null if you don't have the ProGrids package installed, PackageInfo if you have. You get the idea.
     
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    If you check for the package specifically, you'll miss if the project has the github version of the package instead of the original package. I don't know if that is important for the OP's use case, but sometimes people have the source for a package in the Assets folder, and the actual package not installed, because the project requires a slight modification to the package.
     
  4. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    294
    Like I said, I tried that but the list is filled with packages that I don't have installed, and there doesn't seem to be a property in PackageInfo to say whether it's installed, so that list result isn't useful.

    My solution was to read the Packages/manifest.json file and check if the "dependencies" dictionary has a key that matches the package ID I'm checking for. It's fool-proof. I wish the PackageManager library had something built-in though.
     
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    I wouldn't share the example if it weren't working for me, so something is wrong with your editor or I don't know. My example is working in 2020.3.5f1 properly.
     
  6. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    294
    That's good to know that it's working for you. If you don't mind verifying something for me, it would help me feel better about it too... Install the FBX Exporter package, run your checker to verify it's in the list. Then uninstall it and see if it's still in the list. That's the specific package I needed to know was not installed. I'm wondering if installing it makes it get stuck in the list even after removal.
     
  7. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Sure, no problem. Ended up with this:
    screenshot2.png screenshot1.png
    Code (CSharp):
    1. using System.Collections;
    2. using System.Linq;
    3. using UnityEngine;
    4. using UnityEditor.PackageManager;
    5. public class Test : MonoBehaviour
    6. {
    7.     private IEnumerator  Start()
    8.     {
    9.         var pckName = "com.unity.formats.fbx";
    10.      
    11.         var pack = Client.List();
    12.         while (!pack.IsCompleted) yield return null;
    13.         var haveProgrids = pack.Result.FirstOrDefault(q => q.name == pckName);
    14.  
    15.         Debug.Log(haveProgrids == null ? $"I DO NOT have '{pckName}'" : $"I DO have '{pckName}'");
    16.     }
    17. }
    I ran the 'do not have' case both before the first install of the package and after uninstall it and both times it indicated the 'do not have' status, so it seems working properly.
     
  8. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    294
    Gr
    Great, maybe I'll try again for a sanity check. Although, I have to admit that I like my technique of reading the manifest file because it doesn't require asynchronous code.
     
  9. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    294
    By the way, for reference, here's the function I created, which resides in my "CommonEditor.cs" file, which contains several re-usable functions for editor scripts. The JSON class is my own wrapper for JSON parsing, which provides a lot of flexible functionality, but any suitable JSON parser would work.


    Code (CSharp):
    1. public static bool isPackageInstalled(string packageId)
    2. {
    3.     // Since the PackageManager API doesn't seem to have a way to detect if a package is installed,
    4.     // we just check the manifest manually. It's JSON format, so it's easy to read.
    5.     string jsonText = File.ReadAllText("Packages/manifest.json");
    6.  
    7.     JSON json = new JSON(jsonText);
    8.  
    9.     // Since the package names are dictionary keys and have periods in them,
    10.     // we can't reference them using the JSON class's getValue().
    11.     // So, we need to get the raw Dictionary to look at.
    12.     var dependencies = json.getStringStringDict("dependencies");
    13.  
    14.     return dependencies.ContainsKey(packageId);
    15. }
     
  10. Darkgaze

    Darkgaze

    Joined:
    Apr 3, 2017
    Posts:
    374
    But if compilation errors arise because of this, then Version Defines must be used, I guess. Using version "0" in the define?
     
  11. radiatoryang

    radiatoryang

    Joined:
    Jul 7, 2014
    Posts:
    19
    I liked Gillissie's approach, and simplified it further -- just search the contents of manifest.json for a specific substring, no JSON parsing needed. I guess there might be edge cases where the manifest.json format might change, or if certain packages have weird names, but I think it's safe for 99.9% of cases.

    Code (CSharp):
    1. public static bool IsPackageInstalled(string packageId)
    2. {
    3.             if ( !File.Exists("Packages/manifest.json") )
    4.                 return false;
    5.  
    6.             string jsonText = File.ReadAllText("Packages/manifest.json");
    7.             return jsonText.Contains( packageId );
    8. }
     
  12. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,112
    Sorry for bumping this but I think I can clear up the confusion.

    I was trying to find out if shadergraph ("com.unity.shadergraph") was installed and it too was not listed in the results. However, each package in the results has a "dependecies" property and therein I found the missing packages.

    Maybe it's related to them being listed in the manifest as "depth:1" instead of 0 (not sure.)
    Code (CSharp):
    1. {
    2.   "dependencies": {
    3.     "com.unity.ide.rider": {
    4.       "version": "1.2.1",
    5.       "depth": 0,
    6.       "source": "registry",
    7.       "dependencies": {
    8.         "com.unity.test-framework": "1.1.1"
    9.       },
    10.       "url": "https://packages.unity.com"
    11.     },
    12.     "com.unity.shadergraph": {
    13.       "version": "7.7.1",
    14.       "depth": 1,
    15.       "source": "registry",
    16.       "dependencies": {
    17.         "com.unity.render-pipelines.core": "7.7.1"
    18.       },
    19.       "url": "https://packages.unity.com"
    20.     },...

    This code worked for me:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using UnityEditor.PackageManager;
    5. using UnityEditor.PackageManager.Requests;
    6.  
    7. namespace Kamgam
    8. {
    9.    [InitializeOnLoad]
    10.    public class CheckPackages
    11.    {
    12.         static ListRequest Request;
    13.  
    14.         static CheckPackages()
    15.         {
    16.             CheckForPackages();
    17.         }
    18.  
    19.         static void CheckForPackages()
    20.         {
    21.             Request = Client.List(offlineMode: true);
    22.             EditorApplication.update += progress;
    23.         }
    24.  
    25.         static void progress()
    26.         {
    27.             string packageId = "com.unity.shadergraph";
    28.  
    29.             if (Request.IsCompleted)
    30.             {
    31.                 if (Request.Status == StatusCode.Success)
    32.                 {
    33.                     if (!ContainsPackage(Request.Result, packageId))
    34.                     {
    35.                         Debug.Log("Package '" + packageId + "' is missing");
    36.                     }
    37.                 }
    38.                 else if (Request.Status >= StatusCode.Failure)
    39.                 {
    40.                     Debug.Log("Could not check for packages: " + Request.Error.message);
    41.                 }
    42.  
    43.                 EditorApplication.update -= progress;
    44.             }
    45.         }
    46.  
    47.         public static bool ContainsPackage(PackageCollection packages, string packageId)
    48.         {
    49.             foreach (var package in packages)
    50.             {
    51.                 if (string.Compare(package.name, packageId) == 0)
    52.                     return true;
    53.  
    54.                 foreach (var dependencyInfo in package.dependencies)
    55.                     if (string.Compare(dependencyInfo.name, packageId) == 0)
    56.                         return true;
    57.             }
    58.  
    59.             return false;
    60.         }
    61.     }
    62. }
    63.  
    Maybe it will be useful to others :)
     
    Last edited: May 29, 2023
    tokar_dev likes this.
  13. TenaciousDan

    TenaciousDan

    Joined:
    Jul 11, 2019
    Posts:
    22
    This is achievable using version defines in an assembly definition.

    Step 1 (If you don't already have an assembly definition):
    In the project explorer, right click in the project folder that you want to make an assembly out of. In the context menu, select Create > Assembly Definition.

    Step 2:
    Select the Assembly Definition file and in the inspector add the package in question to the list of Assembly Definition references. Here's an example for both TextMeshPro and InputSystem:
    upload_2022-7-16_13-20-52.png

    Step 3:
    Still in the inspector, add the version define. Define property can be whatever you want. Expression should be the minimum version number of the package you are targeting. Example:
    upload_2022-7-16_13-24-13.png

    Now, in your code you can do this:
    upload_2022-7-16_13-27-12.png
    INPUTSYSTEM_1_3 will only be defined if the user has the input system package installed (version 1.3.0 and above).

    This method seems to only work for unity packages. It would be nice if Unity would let you input a string or something for referencing a custom resource instead of the dropdown which only seems to include unity packages.
     
    Last edited: Mar 9, 2023