Search Unity

Question Change scripting define symbols and build the player with a single button press

Discussion in 'Editor & General Support' started by emarc, Nov 21, 2022.

  1. emarc

    emarc

    Joined:
    Dec 2, 2020
    Posts:
    9
    Hi,
    I am trying to store custom build configuration files and execute them with one button. They can have varying scripting define symbols and platform targets.
    According to the docs, I need to return the control back to the editor after updating the scripting define symbols and before building the player:
    See: Custom Scripting Symbols

    How can I do that?
    The only solution I have found was to create a button that applies the settings, and another button that builds the player, so that the Editor definitely regains control after the first button press and can compile the scripts.

    I also tried using CompilationPipeline.RequestScriptCompilation, but apparently using the compilationFinished event to initiate a build is not allowed:
    See: CompilationPipeline.compilationFinished

    Additional experiments with EditorCoroutines and ScriptableBuildPipeline didn't solve the issue either.

    Is there any solution to do all three steps automatically (apply changes, return control to editor, build) with only one button?


    This bug seems to be related: https://issuetracker.unity3d.com/is...t-parameter-doesnt-match-current-build-target
     
  2. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    Using BuildPlayerOptions.extraScriptingDefines could be easier? This only sets scripting defines for the build but doesn't need a recompile in the editor.

    Generally, you can use EditorApplication.delayCall to get yourself out of situations where something is not allowed. E.g. register a
    delayedCall
    callback in
    compilationFinished
    and then trigger the build form there.

    But changing the scripting defines also triggers a domain reload and any callbacks won't survive that. You'll need to store your state as something serializable and then continue when your editor scripts are reloaded after the domain reload.
     
  3. emarc

    emarc

    Joined:
    Dec 2, 2020
    Posts:
    9
    Thank you for the suggestions.
    I tried using
    BuildPlayerOptions.extraScriptingDefines
    , but it does not seem to apply the scripting defines.

    I isolated the relevant code and created a new project using Unity 2021.3.14f1 with the following editor class:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Linq;
    4.  
    5. public class BuildTest : MonoBehaviour
    6. {
    7.     [MenuItem("Test/BuildClient")]
    8.     public static void BuildClient()
    9.     {
    10.         var path = "Builds/Client/Client.exe";
    11.         var scenes = EditorBuildSettings.scenes.Select(x => x.path).ToArray();
    12.         var bpOptions = new BuildPlayerOptions()
    13.         {
    14.             locationPathName = path,
    15.             targetGroup = BuildTargetGroup.Standalone,
    16.             subtarget = (int)StandaloneBuildSubtarget.Player,
    17.             target = BuildTarget.StandaloneWindows64,
    18.             options = BuildOptions.None,
    19.             extraScriptingDefines = new[] { "TEST_CLIENT" },
    20.             scenes = scenes
    21.         };
    22.         var definesText = string.Join(";", bpOptions.extraScriptingDefines);
    23.         Debug.Log($"Extra defines: {definesText}"); // prints: "Extra defines: TEST_CLIENT"
    24.         var report = BuildPipeline.BuildPlayer(scenes, path, bpOptions.target, bpOptions.options);
    25.     }
    26. }
    The resulting executable has a text UI that changes if
    TEST_CLIENT
    is set. In my case, it doesn't, suggesting that TEST_CLIENT is not set.
    Am I missing any steps?


    For completeness, this is the class that changes the text depending on the scripting defines.
    The text should show:
    TEST_CLIENT: true
    TEST_SERVER: false

    but shows:
    TEST_CLIENT: false
    TEST_SERVER: false


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TestText : MonoBehaviour
    4. {
    5.     private void Awake()
    6.     {
    7.         var sb = new System.Text.StringBuilder();
    8.         sb.Append("TEST_CLIENT: ");
    9. #if TEST_CLIENT
    10.         sb.AppendLine("true");
    11. #else
    12.         sb.AppendLine("false");
    13. #endif
    14.         sb.Append("TEST_SERVER: ");
    15. #if TEST_SERVER
    16.         sb.AppendLine("true");
    17. #else
    18.         sb.AppendLine("false");
    19. #endif
    20.         Debug.Log(sb);
    21.         if (TryGetComponent(out TMPro.TextMeshProUGUI textComponent))
    22.         {
    23.             textComponent.text = sb.ToString();
    24.         }
    25.     }
    26. }
     
    Last edited: Nov 22, 2022
  4. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    You're not passing
    bpOtions
    to
    BuildPlayer()
    , only some parts of it. So the value of
    extraScriptingDefines
    never reaches Unity.

    Instead, use the
    BuildPlayer(BuildPlayerOptions buildPlayerOptions)
    overload that directly accepts
    bpOtions
    and nothing else. The scenes and build path are already set on the options, no need to pass them as separate arguments.
    Code (CSharp):
    1. var report = BuildPipeline.BuildPlayer(bpOtions);
     
    emarc likes this.
  5. emarc

    emarc

    Joined:
    Dec 2, 2020
    Posts:
    9
    Thank you for pointing that out! (I also fixed the typo in
    bpOptions
    )

    The only remaining issue is that switching the subtarget (player <-> server) will require the build button to be pressed two times, until UNITY_SERVER is set or unset correctly. But that seems to be the issue discribed in the bug I linked in the first post, so I will probably use the serialize-build-intent-workaround you described until this issue is fixed.

    Edit:
    Apparently, this is all I needed for my second issue. The subtarget can be set manually before calling
    EditorUserBuildSettings.SwitchActiveBuildTarget
    .
    Code (CSharp):
    1. EditorUserBuildSettings.standaloneBuildSubtarget = StandaloneBuildSubtarget.Player;
    2. EditorUserBuildSettings.SwitchActiveBuildTarget(bpOptions.targetGroup, bpOptions.target);
    3. var report = BuildPipeline.BuildPlayer(bpOptions);
     
    Last edited: Nov 22, 2022
  6. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    55
    Apologies for reviving an old thread, but I'm currently facing an issue with using scripting define symbols in Unity. I've followed the guidance mentioned in this thread but failed to use scripting define symboles.

    In my building function, as shown below, I set the `extraScriptingDefines` to `"TEST_B"`:
    Code (CSharp):
    1.  
    2. public static void BuildAPK()
    3. {
    4.     var buildPlayerOptions = new BuildPlayerOptions();
    5.     buildPlayerOptions.targetGroup = BuildTargetGroup.Android;
    6.     buildPlayerOptions.target = BuildTarget.Android;
    7.     buildPlayerOptions.locationPathName = "Release/Test.apk";
    8.     buildPlayerOptions.extraScriptingDefines = new string[] { "TEST_B" };
    9.     var report = BuildPipeline.BuildPlayer(buildPlayerOptions);
    10.  
    11.     if (report.summary.result == BuildResult.Succeeded)
    12.     {
    13.         Debug.Log("Build Success");
    14.     }
    15.     else if (report.summary.result == BuildResult.Failed)
    16.     {
    17.         Debug.LogError("Build Failed");
    18.     }
    19. }
    20.  
    In my testing function (`Awake`), I have conditional compilation based on the define symbols:
    Code (CSharp):
    1.  
    2. private void Awake()
    3. {
    4.     #if TEST_A
    5.         Debug.Log("This is TEST A.");
    6.     #elif TEST_B
    7.         Debug.Log("This is TEST B.");
    8.     #endif
    9. }
    10.  
    However, even when `extraScriptingDefines` is set to `"TEST_B"`, the log output always displays "This is TEST A".
    I was using Unity version 2021.3.8 and defined the symbols in Player Settings > Script Compilation.
    The building function is called by the command line.

    I'm wondering if there's something I'm misunderstanding or if there's an issue with my approach. Thank you in advance for any insights or suggestions you may have to help me resolve this matter.
     
  7. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    If you do get "This is TEST A" in console, then
    TEST_A
    must be defined and overrides
    TEST_B
    . I suspect TEST_A is defined in your player settings?

    As the name implies
    extraScriptingDefines
    are symbols defined in addition to those in player settings, they don't override the existing ones (and it's not possible to un-define a symbol this way).

    If you change your Awake to not use elif, you should see if both A and B are defined:
    Code (CSharp):
    1. private void Awake()
    2. {
    3.     #if TEST_A
    4.         Debug.Log("This is TEST A.");
    5.     #endif
    6.  
    7.     #if TEST_B
    8.         Debug.Log("This is TEST B.");
    9.     #endif
    10. }
     
    TCYeh likes this.
  8. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    55
    Thank you for your response!
    I followed your suggestion and removed the symbols that were previously defined in Project Settings. Instead, I added them solely in extraScriptingDefines, and this resolved the issue. Thank you a lot.

    However, I noticed that by adding the symbols only via script, I no longer have code suggestions or completion for those symbols.
    Is there a way to maintain code suggestion functionality while still triggering the build only with certain symbols?
     
    Last edited: Jul 6, 2023
  9. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    No, only the symbols set in Player Settings will be used in your IDE.

    Whenever possible, I try to compile all code in the editor and only disable parts in the builds, i.e. using
    #if TEST_A || UNITY_EDITOR
    instead. You can't use elif in this case and might have to do editor-only runtime checks but it will prevent compilation errors from suddenly showing up in builds.

    You can also just do runtime checks instead of conditional compilation. In many instances that's much simpler and the overhead of a few ifs here and there is negligible.
     
    TCYeh likes this.
  10. TCYeh

    TCYeh

    Joined:
    Jul 25, 2022
    Posts:
    55
    Thank you for the thorough explanation and suggestions.
    I will consider using runtime checks instead. Much appreciated!
     
  11. AdViventes

    AdViventes

    Joined:
    Dec 17, 2016
    Posts:
    11