Import Package not running when in batchmode

Hey,

I am currently working on a project that needs to download various unitypackages during the build process and import those afterwards. Running my editor scripts within my open unity instance works without a problem, but once I run an automated build with jenkins, using the Unity cli (batchmode, quit and executeMethdo) the AssetDatabase.ImportPackage method does not work anymore.
The Editor simply quits and the Asset-Pack ist not imported!

My code is fairly straight forward:

string targetPath = "Assets/test.unitypackage";
AssetDatabase.ImportPackage(targetPath, false);

Any help is appreciated

The AssetDatabase is off-limits during certain callbacks. One of these is a static ctor or an InitializeOnLoadMethod. I assume it’s in one of these places or the static method that we can call from the command line.

In those cases the fix is as simple as wrapping your code in delayCall:

EditorApplication.delayCall += () => 
{
    // your import code here
};

No need to unsubscribe, the delayCall callback reference is cleared every time it ran.

Thank you for the hint.
I wrapped the import call like so

static void ImportAllModules()
{
    foreach (var moduleName in moduleNames)
    {
        string targetPath = $"Assets/{moduleName}";
        Debug.Log($"Importing Module: {targetPath}");
        EditorApplication.delayCall += () => 
        {
            AssetDatabase.ImportPackage(targetPath, false);
        };
        Debug.Log($"Done Importing Module: {targetPath}");
    }
}

However, I am still facing the same issue as the Editor instantly quits after calling the method.
PS: The Unitypackage contains spites, prefabs, scripts etc.

Change the code to this, it doesn’t make sense to delay every loop iteration:

static void ImportAllModules()
{
	EditorApplication.delayCall += () => 
	{
		foreach (var moduleName in moduleNames)
		{
			var targetPath = $"Assets/{moduleName}";
			Debug.Log($"Importing Module: {targetPath}");
			AssetDatabase.ImportPackage(targetPath, false);
			Debug.Log($"Done Importing Module: {targetPath}");
		}
	};
}

If it still quits, check the editor.log for details. Perhaps it works until it runs into a specific package? If so you should investigate that package further.

Also depending on other code, perhaps it helps to wrap it into delayCall twice, eg delay the delayCall that runs the actual code.

I wouldn’t download the .unitypackage files to the Assets folder. They don’t belong there, they should be located somewhere outside the Assets path to avoid Unity registering them as “assets” and adding them to the AssetDatabase for no reason.

Unfortunately this still doesn’t work.

I update the code to:



static void ImportAllModules()
{
  EditorApplication.delayCall += () => 
  {
      foreach (var moduleName in moduleNames)
      {
          var targetPath = $"{moduleName}";
          Debug.Log($"Importing Module: {targetPath}");
          AssetDatabase.ImportPackage(targetPath, false);
          Debug.Log($"Done Importing Module: {targetPath}");
      }
  };
}

No matter if I wrap the ImportAllModules method into another EditorApplication.delayCall or not, it does not properly import the package. The packages are fine and are now also stored in the workspace directory rather than within the Assets folder.

What about editor.log? Is it crashing after the first import, or with a specific one?

Do any of these packages add scripts? If so, check if they have static ctors or InitializeOnLoad(Method) and check if any of these could fail for some reason (eg using a path that doesn’t exist on the build machine). You may want to add logging to those methods to see how many of these run.

Hey, sorry for the late response. I created a simple workaround by running the UnityEditor from the Cli via the -importPackage argument.
Obviously this is not optimal as this has to be done for every package that is imported.

No matter the way I write the import code (with EditorApplication.delaycall or not) it does not work. The Log is always the same
f.e.

foreach (var moduleName in testnames)
{
    var targetPath = Path.Combine(workspacePath, moduleName);
    Debug.Log($"Importing Module: {targetPath}");

    if (File.Exists(targetPath))
    {
        EditorApplication.delayCall += () => 
        {
                AssetDatabase.ImportPackage(targetPath, false);
        };

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }
    else
    {
        Debug.LogError($"Module not found at path: {targetPath}");
    }
}

Always results in this editor log:

Importing Module: ${CensoredPathToWorkspace}\Packagename.unitypackage
UnityEngine.Debug:ExtractStackTraceNoAlloc (byte*,int,string)
UnityEngine.StackTraceUtility:ExtractStackTrace ()
UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
UnityEngine.Logger:Log (UnityEngine.LogType,object)
UnityEngine.Debug:Log (object)
${Censored}
${Censored}
(Filename: ${Censore}.cs Line: 30)

Asset Pipeline Refresh (id=bbff9875456b54e49936888be7e7ca88): Total: 0.048 seconds - Initiated by RefreshV2(NoUpdateAssetOptions)
Finished importing all packages.
UnityEngine.Debug:ExtractStackTraceNoAlloc (byte*,int,string)
UnityEngine.StackTraceUtility:ExtractStackTrace ()
UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
UnityEngine.Logger:Log (UnityEngine.LogType,object)
UnityEngine.Debug:Log (object)
${Censored}
${Censored}

(Filename: ${Censored}.cs Line: 49)

[Physics::Module] Cleanup current backned.
[Physics::Module] Id: 0xf2b8ea05
[Licensing::IpcConnector] License Notification channel disconnected successfully.
[Licensing::IpcConnector] License Client channel disconnected successfully.
AcceleratorClientConnectionCallback - disconnected - :0
Shut down.
Cleanup mono
abort_threads: Failed aborting id: 0000000000002164, mono_thread_manage will ignore it

abort_threads: Failed aborting id: 00000000000019F4, mono_thread_manage will ignore it

abort_threads: Failed aborting id: 0000000000002164, mono_thread_manage will ignore it

abort_threads: Failed aborting id: 00000000000019F4, mono_thread_manage will ignore it

debugger-agent: Unable to listen on 3424
Checking for leaked weakptr:
  Found no leaked weakptrs.
[Package Manager] Server process was shutdown

Edit: This happens no matter what the unitypackage contains (whether it contains tons of scripts, a single script without any Function or just a material)

You are importing packages. What part of importing a package do you think remains “unsaved”? There is no need to save the project.

Likewise, the AssetDatabase methods needn’t be followed up by a “Refresh”. The internal state is already updated if you modify files/folders only via AssetDatabase methods.

And you will most certainly not want to repeat both of the above for every package.

You do notice the fallacy of this code?

  1. Execution of ImportPackage is delayed
  2. Saving project & Refresh
  3. Package gets imported

:wink:

The errors may be because the saving and refreshing are not delayed and if you run AssetDabase methods in a static ctor or InitializeOnLoad that’s what could happen (Unity has the cases where AssetDatabase calls are invalid as “throwing exceptions in the future” but I don’t think it’s been implemented yet).

Try this code instead:

foreach (var moduleName in testnames)
{
    var targetPath = Path.Combine(workspacePath, moduleName);
    Debug.Log($"Importing Module: {targetPath}");

    if (File.Exists(targetPath))
    {
        EditorApplication.delayCall += () => 
        {
                AssetDatabase.ImportPackage(targetPath, false);
        };
    }
    else
    {
        Debug.LogError($"Module not found at path: {targetPath}");
    }
}

The fallacy probably resulted in countless attempts of importing the packages. But yes, I do see the probleme there.

However, even when stripping down the code to the most basic few lines for importing I still get the same result as posted above:

public static void ImportSample()
{
    string workspacePath = Environment.GetEnvironmentVariable("WORKSPACE");
    Debug.Log("Starting import of package...");
    var targetPath = Path.Combine(workspacePath, "material.unitypackage");
    EditorApplication.delayCall += () => 
    {
        AssetDatabase.ImportPackage(targetPath,false);
    };
}

The package only contains a single Material in a single folder. No scripts with ctors or anything.
And yes, the script can definitely find the file at the correct location.

But it still results in the following output:

Starting import of packages..
UnityEngine.Debug:ExtractStackTraceNoAlloc (byte*,int,string)
UnityEngine.StackTraceUtility:ExtractStackTrace ()
UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
UnityEngine.Logger:Log (UnityEngine.LogType,object)
UnityEngine.Debug:Log (object)

(Filename: ${Censored}.cs Line: 19)

Batchmode quit successfully invoked - shutting down!
Curl error 42: Callback aborted
[Physics::Module] Cleanup current backned.
[Physics::Module] Id: 0xf2b8ea05
[Licensing::IpcConnector] License Notification channel disconnected successfully.
[Licensing::IpcConnector] License Client channel disconnected successfully.
AcceleratorClientConnectionCallback - disconnected - :0
Shut down.
Cleanup mono

Not sure if maybe this has something to do with the Unity version? (6000.0.23f1), but I don’t think so.

delayCall when running with -quit sounds like it’ll do exactly what you suspect - it’ll quit the editor before the delay call hits. If you need to do delayCall shennanigans, I’d remove the -quit flag and instead do EditorApplication.Exit() after all your code has finished. Then you can wait for an asynchronous process to finish.

I’m interested to know what kind of problem downloading and importing unitypackages during the build process solves, though!

2 Likes

Thank you for the input.
Yes, it indeed was the problem that my building script had the -quit flag appended to the command.
I got the importing to work and are currently trying to figure out how to get a notification once all packages are imported. Due to the fact that they contain various scripts, callbacks like

AssetDatabase.importPackageCompleted += OnImportCompleted;

do not work properly.

Hook into AssemblyReloadEvents?

Use a ScriptableSingleton as part of or as the central class of your processing to allow fields to survive domain reload without having to use (and possibly reset) EditorPrefs.

For everyone wondering, that comes to this Post in the future.
I resolved the “notification on load” with the following class

[InitializeOnLoad]
public class PackageRegistrar : AssetPostprocessor
{
    static PackageRegistrar()
    {
        Debug.Log("PackageRegistrar: s_ctor");
        AssetDatabase.importPackageStarted += OnImportPackageStarted;
        AssetDatabase.importPackageCompleted += OnImportPackageCompleted;
    }

    private static void OnImportPackageStarted(string packagename)
    {
        Debug.Log($"PackageRegistrar: OnImportPackageStarted({packagename})");
    }

    private static void OnImportPackageCompleted(string packagename)
    {
        var guids = AssetDatabase.FindAssets("t:prefab", new[] {"Assets/TargetFolder/Prefabs" });
        ...
    }
}

This simply checks how many prefabs (in my case) have already been imported and is compared to the number of packages that are being imported totally.
Surely not the best solution, but the fastest one I was able to implement for my case.
Once I have more time to dig deeper, I will edit this post / reply with a better solution.