Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Build Pipeline & Target Platform Issues

Discussion in 'Testing & Automation' started by SomeGuy22, May 2, 2022.

  1. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I really wanted to be able to automate building for all platforms using the Build Pipeline but it seems a few snags are confusing me and I'm not sure how to proceed or what this pipeline API is really doing under the hood.

    To start with, this is the code I use when I want to build for a certain target:

    Code (CSharp):
    1.  
    2.     static BuildReport BuildPlayerForTarget(BuildTarget buildTarget, string path, string subdirPlatformName, string extension)
    3.     {
    4.         var buildOptions = BuildOptions.None;
    5.         var finalPath = path + "/" + gameFolderShortName + subdirPlatformName;
    6.         finalPath = finalPath + "/" + Application.productName + extension;
    7.  
    8.         Debug.Log("BuildTarget Player Started: " + buildTarget.ToString() + " | " + finalPath);
    9.  
    10.         return BuildPipeline.BuildPlayer(EditorBuildSettings.scenes, finalPath, buildTarget, buildOptions);
    11.     }
    This gets run 3 times (PC, Mac, and Linux) when I use my custom menu option for building on preset options.

    Issue #1 - No Linux Mono Build Target?

    For the Linux run, I use BuildTarget.StandaloneLinux64, which according to this is the only supported Linux target currently. But when I ran it, it started complaining about IL2CPP not being installed. Checking my modules, it was correct. I only had Linux (Mono) installed. I looked through the build options but I couldn't find anything that would let me target Mono instead of CPP from the BuildPipeline API, even though I'd assume I could still target Mono from the usual build popup window. So I went ahead and installed IL2CPP support:



    Running it again, I encountered this error:

    Code (CSharp):
    1. Exception: C++ code builder is unable to build C++ code for Linux: Could not find valid clang executable at clang.exe
    2. UnityEditorInternal.Runner.RunProgram (UnityEditor.Utils.Program p, System.String exe, System.String args, System.String workingDirectory, UnityEditor.Scripting.Compilers.CompilerOutputParserBase parser) (at <55729f52d042492e9efc384182ae2feb>:0)
    3. UnityEditorInternal.Runner.RunManagedProgram (System.String exe, System.String args, System.String workingDirectory, UnityEditor.Scripting.Compilers.CompilerOutputParserBase parser, System.Action`1[T] setupStartInfo) (at <55729f52d042492e9efc384182ae2feb>:0)
    4. UnityEditorInternal.IL2CPPBuilder.RunIl2CppWithArguments (System.Collections.Generic.List`1[T] arguments, System.Action`1[T] setupStartInfo) (at <55729f52d042492e9efc384182ae2feb>:0)
    5. UnityEditorInternal.IL2CPPBuilder.ConvertPlayerDlltoCpp (UnityEditor.Il2Cpp.Il2CppBuildPipelineData data) (at <55729f52d042492e9efc384182ae2feb>:0)
    6. UnityEditorInternal.IL2CPPBuilder.Run () (at <55729f52d042492e9efc384182ae2feb>:0)
    7. UnityEditorInternal.IL2CPPUtils.RunIl2Cpp (System.String stagingAreaData, UnityEditorInternal.IIl2CppPlatformProvider platformProvider, System.Action`1[T] modifyOutputBeforeCompile, UnityEditor.RuntimeClassRegistry runtimeClassRegistry) (at <55729f52d042492e9efc384182ae2feb>:0)
    8. DesktopStandalonePostProcessor.RunIL2CPP (UnityEditor.Modules.BuildPostProcessArgs args, UnityEditorInternal.IIl2CppPlatformProvider il2cppPlatformProvider, System.Collections.Generic.List`1[T] cppPlugins) (at <55729f52d042492e9efc384182ae2feb>:0)
    9. DesktopStandalonePostProcessor.SetupStagingArea (UnityEditor.Modules.BuildPostProcessArgs args, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at <55729f52d042492e9efc384182ae2feb>:0)
    10. DesktopStandalonePostProcessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at <55729f52d042492e9efc384182ae2feb>:0)
    11. Rethrow as BuildFailedException: Exception of type 'UnityEditor.Build.BuildFailedException' was thrown.
    12. DesktopStandalonePostProcessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at <55729f52d042492e9efc384182ae2feb>:0)
    13. UnityEditor.Modules.DefaultBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args, UnityEditor.BuildProperties& outProperties) (at <55729f52d042492e9efc384182ae2feb>:0)
    14. UnityEditor.PostprocessBuildPlayer.Postprocess (UnityEditor.BuildTargetGroup targetGroup, UnityEditor.BuildTarget target, System.String installPath, System.String companyName, System.String productName, System.Int32 width, System.Int32 height, UnityEditor.BuildOptions options, UnityEditor.RuntimeClassRegistry usedClassRegistry, UnityEditor.Build.Reporting.BuildReport report) (at <55729f52d042492e9efc384182ae2feb>:0)
    15. UnityEditor.BuildPipeline:BuildPlayer(EditorBuildSettingsScene[], String, BuildTarget, BuildOptions)
    This other forum post made me start to worry that it wasn't waiting for a platform switch to complete before building the player, however I also saw now that I didn't install the sysroot toolchain package:



    Fair enough I guess, but now I don't see any option to revert back to Mono build to quickly get around this. There's nothing in this window so did IL2CPP just override it? Is the only way to target Mono again to uninstall the IL2CPP package? There isn't any option within Unity Hub to do that so I'd have to go digging around in the files which is a bit of a pain. To me it's odd that having both modules installed doesn't give you an option for either so any insight on this is appreciated.

    Issue #2 - .app not working for Mac?

    I have noticed as well that BuildPlayer seems to just not run when I target StandaloneOSX. I suspect that this has to do with some awkward behavior I've noticed when building normally in the past. When building for Windows or Linux, you are asked to put in a file name and it saves under .exe or .x86 etc. But when building for Mac from the Build menu it asks for a folder, and it doesn't dump the .app into that folder but instead creates a duplicate folder with the same name but with the .app extension and puts the contents into that.

    So could this be why the BuildPipeline is failing? Does it expect me to give it a plain folder name (without .app) where it will create a new one with .app at the end? I'm curious about the specifics of what the BuildPlayer function is trying to do here.

    Issue #3 - Asset errors on Linux only

    When I build for linux, a separate issue occurs, timestamped before the C++ code builder error.

    Code (CSharp):
    1. The asset at Assets/Textures/UI/Fonts/SubtitleFonts/NotoSans-BoldItalic.ttf has been scheduled for reimport during the Refresh loop and Loading of it has been attempted.
    2. Doing this can lead to the AssetDatabase returning two versions of the same asset.
    3. Please ensure that code which attempts to reimport this asset does not run while the editor is Refreshing.
    4. You can do so by checking the value of EditorApplication.isUpdating
    5. UnityEditor.BuildPipeline:BuildPlayer (UnityEditor.EditorBuildSettingsScene[],string,UnityEditor.BuildTarget,UnityEditor.BuildOptions)
    Could this be because BuildPlayer didn't wait for target platform to switch like in the above post I linked? I don't think I have any code that would be touching those assets at this time, so I'm not sure why they were scheduled for re-import. I will do more testing on manual builds to see if this is still the case outside of BuildPipeline API.

    Thanks for any insight and help, the Build Pipeline is really neat but I'd like to be able to understand it better and avoid problems like this to help speed up and streamline my deployment.
     
    theMatan123 likes this.
  2. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Seems like I might've been a little hasty in asking for help when I was just able to do more experiments that clear some stuff up for me. Still, I think this is important stuff to document since I haven't seen much about it online.

    To start for Issue #2: yes my hunch was correct. BuildPipeline for Standalone OSX expects that you give it a folder name, not a .app, even though the final result is a .app. I won't really understand why it works like that but it just does, mirroring how it works in the regular build menu.

    Running my Mac build I now got this error:
    Code (CSharp):
    1. Error building Player: Currently selected scripting backend (IL2CPP) is not installed.
    Which started to lead me down a path of understanding for this whole thing. In Issue #1 I wondered why there wasn't an option to switch between Mono and IL2CPP. Since I knew there was no IL2CPP target for Mac, the "currently selected" part eventually led me to the Player section of the project settings. That's when I realized it is actually drawing from this option to determine how it will build, not just how it would run in Editor like I originally thought.

    So here's a way to fix my Mac build while also being able to select from either IL2CPP or Mono for Linux! Now I needed a way to do this programmatically so that I wouldn't have to switch it manually, and sure enough there is a function to do just that (though it was a pain to track down and yet again I couldn't find any discussion about this online).

    Code (CSharp):
    1. PlayerSettings.SetScriptingBackend(targetGroup, scriptBackend);
    All that was left to do is extend my build target data holder to take in more data, including which backend to use for each target, ends up looking something like this:

    Code (CSharp):
    1.  
    2.     static readonly AutoBuildTarget[] autoBuildTargets =
    3.     {
    4.         new AutoBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64, ScriptingImplementation.IL2CPP,
    5.             "Windows", ".exe"),
    6.         new AutoBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneOSX, ScriptingImplementation.Mono2x,
    7.             "Mac", ""),
    8.         new AutoBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneLinux64, ScriptingImplementation.Mono2x,
    9.             "Linux", ".x86_64"),
    10.     };
    Then I revamped my build function to use the new BuildPlayerOptions class (old way is being deprecated) and to automatically set the correct scripting backend:

    Code (CSharp):
    1. static BuildReport BuildPlayerForTarget(AutoBuildTarget v, string parentPath)
    2.     {
    3.         var buildOptions = BuildOptions.None;
    4.        
    5.         var finalPath = parentPath + "/" + Application.productName + v.extension;
    6.  
    7.         Debug.Log("BuildTarget Player Started: " + v.target.ToString() + " | " + finalPath);
    8.  
    9.         PlayerSettings.SetScriptingBackend(v.targetGroup, v.scriptBackend);
    10.  
    11.         BuildPlayerOptions player = new BuildPlayerOptions();
    12.         var sceneList = new List<string>();
    13.         for(int i = 0; i < EditorBuildSettings.scenes.Length; i++)
    14.         {
    15.             if(EditorBuildSettings.scenes[i].enabled)
    16.             {
    17.                 sceneList.Add(EditorBuildSettings.scenes[i].path);
    18.             }
    19.         }
    20.         player.scenes = sceneList.ToArray();
    21.         player.locationPathName = finalPath;
    22.         player.options = buildOptions;
    23.         player.targetGroup = v.targetGroup;
    24.         player.target = v.target;
    25.        
    26.         return BuildPipeline.BuildPlayer(player);
    27.     }
    With all this I was finally able to get the Mac build working! And I'll also assume Linux won't have that c++ error now that I can target Mono instead. The only thing that remains to be seen is whether those final asset import issues will appear again once I try to run all 3 targets at once, so I'll report back on how that goes later.
     
    theMatan123 likes this.
  3. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    After running some more builds I was able to confirm Issue #3 is still present, and yet again my hunch was correct. It seems to be that running BuildPlayer while on a different platform than the one I'm targeting always causes these asset errors. When I build for the same platform (currently selected in the standard build menu) it doesn't pop up. And this seems to be the case for all 3 platforms.

    Should also be noted that after running BuildPipeline it leaves the currently selected platform as the one I target which isn't a problem, but that may also impact editor playmode performance since I'm switching the scripting backend(?)

    I also tested the build itself after having these errors occur and it seems to not affect it at all, but that may just be because those specific fonts weren't used. I'm guessing it won't be a real problem but it's weird that this happens at all. Is this usual behavior for the BuildPipeline? I feel like building for a different platform than currently selected is a common case and yet it results in these errors.

    So an obvious workaround is to just switch platforms manually and run each build target one at a time, but then that kind of defeats the point of automating the entire build process. Another possible solution - what if I was to use an Editor call to manually switch targets programmatically and then run the BuildPlayer call? If everything is set up atomically then it should be no different than what I'm doing switching it by hand right? Just weird that I would have to do that since I haven't seen any examples online take this into account.

    Could it be related to this little snippet from the docs?

    This seems incredibly dangerous! Could this be the source of the asset import errors?
     
    theMatan123 likes this.
  4. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I tried target switching manually and still received the asset reimport errors. Seems to be a similar case to this thread. I'm still confused on whether I need to target switch before running BuildPlayer for another platform or not, because now I'm thinking this is just a target switching error not a BuildPlayer issue. And the reason I was seeing it before could be because BuildPlayer is calling a target switch internally.

    So maybe I'm in the clear without needing to prep extra steps before BuildPipeline runs, but I'll do more research on it just in case. For now I'm just switching platforms by hand and building one at a time with BuildPlayer to take advantage of automatic folder naming and setting the scripting backend via code.

    EDIT: Neat trick I've learned is that you can actually view the C# reference on Github, so according to this code from the BuildPlayer function it seems they do call SwitchActiveBuildTargetAsync() internally from BuildPlayer. And it has some system to wait for compilation too.

    So even though the docs warn about scripting symbols not changing, I'm guessing that this isn't actually an issue and I should be able to just call BuildPlayer whenever. Still would like to eliminate or at least understand those import errors though which seem to be related to switching target rather than build.
     
    Last edited: May 4, 2022
    theMatan123 likes this.