I am using IL2CPP in my project and use a Windows 10 machine for my development. I don’t have any problems building the game for Linux Target using Editor interface (I have installed sysroot component for this purpose).
However I have some issues with building for Linux using BuildPipeline.BuildPlayer; More specifically, when the active build target (the selected target in the Build Setting window) is Windows and I call BuildPipeline.BuildPlayer I get the “Exception: C++ code builder is unable to build C++ code for Linux: Could not find valid clang executable at clang.exe” error in console and “Building linux il2cpp player requires a linux sysroot package to be installed” in Build Settings window; I don’t see these errors and successfully build the Linux version when I manually change the active target to Linux before calling BuildPipeline.BuildPlayer.
I wanted to first report this issue and then ask if there are any work arounds I can use so that I can use BuildPipeline.BuildPlayer without manual intervention?
I investigated this issue for a bit; It seems BuildPipeline.BuildPlayer does not wait for InitializePlatformSupportModules to finish its job before starting to build the game.
It appears to me that InitializePlatformSupportModules is not completely baked into the target change handling code because even in Built Settings window, when I change the target to Linux, sometime it shows the "Building linux il2cpp player requires a linux sysroot package to be installed" error for a short period then removes it and enables the build buttons.
So one possible workaround for my problem - other than fixing the bug - is that in my script:
Change the active target to Linux using EditorUserBuildSettings.SwitchActiveBuildTarget
Wait for InitializePlatformSupportModules for finish its job then
Then call the BuildPipeline.BuildPlayer
But I am not sure if there are any events I can listen to in order to find out InitializePlatformSupportModules has finished its job. I tried to use Progress class for this but it is not working the way I want; After the call to EditorUserBuildSettings.SwitchActiveBuildTarget, the “Background Tasks Progress Indicator” (tiny circle usually at the bottom right corner of the screen) shows progress but the tasks list is empty so the Progress events are not get called when the hidden tasks (which I think is InitializePlatformSupportModules finishes).
It seems BuildPipeline.BuildPlayer does not even try to initialize sysroot modules at all; I am not even sure if it calls UnityEditor.Modules.ModuleManager:InitializePlatformSupportModules.
Apparently the module initialization (i.e. calling UnityEditor.Modules.ModuleManager:InitializePlatformSupportModules) that happens after changing targets using EditorUserBuildSettings.SwitchActiveBuildTarget resets many things in the editor which makes me unable to wait for module initializations before calling BuildPipeline.BuildPlayer; For example:
Handlers for Application.logMessageReceived won’t get called after module initialization; It seems all the subscribers to this C# event are get removed.
The EditorCoroutines stop working when the module initialization happens.
Bring this post up for I’m meeting same issue too. Using teamcity for automatic build and it now block at linux IL2CPP build, saying: “Exception: C++ code builder is unable to build C++ code for Linux: Could not find valid clang executable at clang.exe” though the package is actually in the project.
So… with the info above, I guess add build target parameter in headless command(or set this parameter in teamcity build step configurations) should solve it?
In teamcity it says “Specify an active build target before loading the project.”. Not sure with the command line will this work.
I’ve managed to build for Linux without error using BuildPlayer by waiting till after domain reload.
Note: In my case I am starting the builds from the menu. Did not test batch building all target platforms from menu yet but I guess it will involve more saving to file and waiting for reloads.
Save to file the info about what it is you want to build.
Do EditorUserBuildSettings.SwitchActiveBuildTarget
Have a [InitializeOnLoadMethod] method that adds another to EditorApplication.delayCall
In the delay called method you read the saved file to find what must be build, and do the build (delete the saved file!)
Something like this …
private static void Build(Store store, Target target, bool demo)
{
// store info about what should be build
if (!SaveBuildInfo(new BuildInfo { store = store, target = target, demo = demo }))
{
return;
}
// start build now if active build target is correct
if (EditorUserBuildSettings.activeBuildTarget == ToBuildTarget(target))
{
Build();
}
// else, switch to correct target. this will trigger a domain relaod during which the build will start
else
{
EditorUserBuildSettings.SwitchActiveBuildTarget(ToBuildTargetGroup(target), ToBuildTarget(target));
}
}
[InitializeOnLoadMethod]
private static void CheckBuildOnLoad()
{
EditorApplication.delayCall += Build;
}
private static void Build()
{
EditorApplication.delayCall -= Build;
var nfo = LoadBuildInfo();
if (nfo == null) return;
// get rid of the file now so that next domain load don't trigger a build
ClearBuildInfo();
if (EditorUserBuildSettings.activeBuildTarget != ToBuildTarget(nfo.target))
{
Debug.LogError($"Build failed: {EditorUserBuildSettings.activeBuildTarget} != {ToBuildTarget(nfo.target)}");
return;
}
..
..
..
var report = BuildPipeline.BuildPlayer(ops);
..
..
}
private static void ClearBuildInfo()
{
var path = Application.dataPath.Replace("\\", "/").Replace("/Assets", $"/Temp/ply_build_info.json");
if (File.Exists(path))
{
try
{
File.Delete(path);
}
catch
{
Debug.LogError("Failed to delete build info file.");
}
}
}
private static bool SaveBuildInfo(BuildInfo nfo)
{
var path = Application.dataPath.Replace("\\", "/").Replace("/Assets", $"/Temp/ply_build_info.json");
var json = JsonUtility.ToJson(nfo);
try
{
File.WriteAllText(path, json);
return true;
}
catch
{
Debug.LogError("Failed to write build info file.");
return false;
}
}
private static BuildInfo LoadBuildInfo()
{
var path = Application.dataPath.Replace("\\", "/").Replace("/Assets", $"/Temp/ply_build_info.json");
if (File.Exists(path))
{
try
{
var json = File.ReadAllText(path);
return JsonUtility.FromJson<BuildInfo>(json);
}
catch
{
Debug.LogError("Failed to load build info file.");
return null;
}
}
else
{
return null;
}
}