Need help on Addressables build automation

Good day!
I have a project set up using Addressables for multiple platforms (Windows, iOS, and Android). However, I have to switch platforms individually and then build Addressables for each one, which is very time-consuming. I’m looking for an automated way to handle this process. I created an editor script to switch between different platforms and build Addressables, but the script seems to have some issues—it breaks in the middle and throws errors. If anyone has a workaround for this, I would greatly appreciate your help.

Editor Script:

using UnityEngine;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.AddressableAssets.Build;

public class BuildEditor
{
    public enum PLATFORM { WINDOWS, ANDROID, IOS }
    public static PLATFORM CURRENT_PLATFORM = PLATFORM.WINDOWS;
    private static bool isWaitingForPlatformSwitch = false;
    private static bool isWaitingForCompilation = false;
    private static Queue<PLATFORM> platformQueue = new Queue<PLATFORM>();

    [MenuItem("Tools/GlobalBuild")]
    public static void BuildGlobal()
    {
        platformQueue.Enqueue(PLATFORM.ANDROID);
        platformQueue.Enqueue(PLATFORM.IOS);
        platformQueue.Enqueue(PLATFORM.WINDOWS);

        ProcessNextPlatform();
    }

    private static void ProcessNextPlatform()
    {
        if (platformQueue.Count > 0)
        {
            CURRENT_PLATFORM = platformQueue.Dequeue();
            Debug.Log("Processing platform: " + CURRENT_PLATFORM);

            switch (CURRENT_PLATFORM)
            {
                case PLATFORM.ANDROID:
                    SetPlatformAndroid();
                    break;
                case PLATFORM.IOS:
                    SetPlatformIOS();
                    break;
                case PLATFORM.WINDOWS:
                    SetPlatformWindows();
                    break;
            }
        }
        else
        {
            Debug.Log("Platform build process completed.");
        }
    }

    [MenuItem("Tools/Build/Windows")]
    public static void SetPlatformWindows()
    {
        UpdateConfigsToPC();
        SwitchPlatform(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
    }

    [MenuItem("Tools/Build/IOS")]
    public static void SetPlatformIOS()
    {
        UpdateConfigsToMobile();
        SwitchPlatform(BuildTargetGroup.iOS, BuildTarget.iOS);
    }

    [MenuItem("Tools/Build/Android")]
    public static void SetPlatformAndroid()
    {
        UpdateConfigsToMobile();
        SwitchPlatform(BuildTargetGroup.Android, BuildTarget.Android);
    }
    private static BuildTarget targetBuildTarget;
    private static void SwitchPlatform(BuildTargetGroup targetGroup, BuildTarget buildTarget)
    {
        if (EditorUserBuildSettings.activeBuildTarget != buildTarget)
        {
            Debug.Log($"Switching platform to {buildTarget}...");
            isWaitingForPlatformSwitch = true;
            targetBuildTarget = buildTarget;  // Store the target build target for checking after switching

            // Trigger platform switch
            EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, buildTarget);

            // Register update callback to wait for platform switch completion
            EditorApplication.update += WaitForPlatformSwitch;
        }
        else
        {
            // Platform is already set, validate Addressable settings and build Addressables
            ValidateAndBuildAddressables();
        }
    }

    private static void WaitForPlatformSwitch()
    {
        // Check if the platform switch is complete by comparing the active build target with the target build target
        if (EditorUserBuildSettings.activeBuildTarget == targetBuildTarget)
        {
            Debug.Log("Platform switch complete.");
            isWaitingForPlatformSwitch = false;
            EditorApplication.update -= WaitForPlatformSwitch;

            // After platform switch, validate Addressable settings and build Addressables
            ValidateAndBuildAddressables();
        }
    }

    // Method to validate Addressable settings and trigger Addressable build
    private static void ValidateAndBuildAddressables()
    {
        if (AddressableAssetSettingsDefaultObject.Settings == null)
        {
            Debug.LogError("Addressable settings are null. Make sure Addressable settings are properly configured before building.");
            return;
        }

        // Ensure Addressable groups are properly configured
        if (AddressableAssetSettingsDefaultObject.Settings.groups.Count == 0)
        {
            Debug.LogError("No Addressable groups are configured. Please configure Addressable groups before building.");
            return;
        }

        // At this point, Addressable settings are valid. Proceed to build Addressables.
        BuildAddressables();
    }

    public static void BuildAddressables()
    {
        if (isWaitingForPlatformSwitch)
        {
            Debug.Log("Waiting for platform switch to complete...");
            return;
        }

        if (EditorApplication.isCompiling)
        {
            if (!isWaitingForCompilation)
            {
                Debug.Log("Waiting for compilation to finish...");
                isWaitingForCompilation = true;
                CompilationPipeline.compilationFinished += OnCompilationFinished;
            }
            return;
        }

        //PerformAddressablesBuild();
    }

    private static void OnCompilationFinished(object obj)
    {
        Debug.Log("Compilation finished. Proceeding to build Addressables...");
        isWaitingForCompilation = false;
        CompilationPipeline.compilationFinished -= OnCompilationFinished;

        PerformAddressablesBuild();
    }

    private static void PerformAddressablesBuild()
    {
        Debug.Log("Building Addressables for platform: " + EditorUserBuildSettings.activeBuildTarget);

        // Build Addressables
        AddressableAssetSettings.CleanPlayerContent();

        try
        {
            BuildAddressablesWithProgress();
           // AddressableAssetSettings.BuildPlayerContent();
            Debug.Log("Building Addressables DONE!");

            // After successful Addressables build, move to the next platform (if applicable)
            ProcessNextPlatform();
        }
        catch (System.Exception ex)
        {
            Debug.LogError("Addressables build failed with an exception: " + ex.Message);
        }
      
    }

    public static void UpdateConfigsToMobile()
    {
        string[] guids = AssetDatabase.FindAssets("t:SceneConfig");
        foreach (string guid in guids)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            SceneConfig config = AssetDatabase.LoadAssetAtPath<SceneConfig>(path);
            if (config != null)
            {
                config.CurrentPlatform = SceneConfig.Platform.MOBILE;
                EditorUtility.SetDirty(config);
            }
        }
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        Debug.Log("Updated all SceneConfig assets to Mobile platform.");
    }

    public static void UpdateConfigsToPC()
    {
        string[] guids = AssetDatabase.FindAssets("t:SceneConfig");
        foreach (string guid in guids)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            SceneConfig config = AssetDatabase.LoadAssetAtPath<SceneConfig>(path);
            if (config != null)
            {
                config.CurrentPlatform = SceneConfig.Platform.PC_MAC;
                EditorUtility.SetDirty(config);
            }
        }
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        Debug.Log("Updated all SceneConfig assets to PC platform.");
    }



    private static float buildProgress = 0.0f;

    // Method to start the Addressables build and track progress
    public static void BuildAddressablesWithProgress()
    {
        Debug.Log("Starting Addressables Build with Progress...");

        // Start the asynchronous Addressables build
        EditorApplication.update += TrackAddressablesBuildProgress;

        // Trigger Addressables build
        AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);

        if (result == null || !string.IsNullOrEmpty(result.Error))
        {
            Debug.LogError("Addressables build failed: " + result?.Error);
        }
        else
        {
            Debug.Log("Addressables Build Completed Successfully.");
        }

        // Stop the progress tracking
        EditorApplication.update -= TrackAddressablesBuildProgress;
        EditorUtility.ClearProgressBar();  // Ensure the progress bar is cleared
    }

    // Track Addressables build progress (simulated)
    private static void TrackAddressablesBuildProgress()
    {
        // Update progress (this is simulated as Unity doesn't give direct progress for Addressables build)
        buildProgress += 0.01f;

        // Display the progress bar
        EditorUtility.DisplayProgressBar("Building Addressables", $"Progress: {buildProgress * 100:F2}%", buildProgress);

        // Simulate a completion of progress
        if (buildProgress >= 1.0f)
        {
            Debug.Log("Addressables Build Progress: 100%");
            EditorUtility.ClearProgressBar();
            EditorApplication.update -= TrackAddressablesBuildProgress;
        }
    }
}

You can’t wait for the platform switch or compilation ending the way you are doing. Those involve a domain reload at the end, at which point all instances are destroyed, all static fields are reset, and then Unity only partially recreates supported instances and fields from serialized data. Anything not supported by Unity serialization, like Queue or delegates on EditorApplication.update, will always be cleared.

You have to create your queue in a way that can survive domain reloads. The easiest I found is to create a scriptable object instance, set its HideFlags so that it doesn’t get garbage collected, and then limit yourself to types that can be serialized in Unity (this is a Hot Reload situation, so private fields will be serialized as well). Most notably, you won’t be able to use any delegates and will have to use custom serialization if you want to use collections.

Hi Adrian,

Thank you for your reply. I’ll check this approach and get back to you with the results. Additionally, I came across some threads here where people used shell scripts to build Addressables for multiple platforms . Could you let me know which method is better?

I opted for this workaround because I can visually see what’s happening and debug the issue inside Unity, instead of just checking a log file. However, I’m curious if there are specific reasons to build Addressables externally through shellscript. Please let me know.

Either approach will work.

A Unity native solution can be a bit tricky to set up but once you have it working, it’s not too complex. It also avoids having an external dependency (the shell / scripting language) and allows you to have better feedback when building inside the editor.

The script approach is potentially a bit slower, since Unity needs to shut down and load the project between every build target. But you don’t have to worry about domain reloads. And, if you already have a CI environment, this approach might be more natural.