Build Error on Linux

Hi all,

I understand Cesium for Unity does not consider Linux a supported platform, but I am trying to incorporate this dependency into our existing CI process which is based off Linux (Ubuntu). From reading the docs and forums it sounds like building from source on Linux should be possible, and after a few days of poking at this, I have it building successfully, but running into an issue when packaging into my project and building an apk.

I took a read through this thread, which was helpful:

I am doing something similar, where I install all the dependencies in a docker - cmake 3.27.1, .NET V7, and I’m running a headless Unity 2022.3.4f1 to compile. I build reinterop and then run CesiumForUnity.BuildCesiumForUnity.CompileForEditorAndExit using the sample projects repo, that produces the .so files and everything seems to be fine. That’s the last step of my docker, then I copy over the entire com.cesium.unity folder from the sample project’s Packages into my project’s Packages folder, then I run my build.

The build is “successful” but the resulting apk throws an error when I run it on the device (Quest Pro):

NotImplementedException: The native implementation is missing so Update cannot be invoked. This may be caused by a missing call to CreateImplementation in one of your constructors, or it may be that the entire native implementation shared library is missing or out of date.

I also noticed these errors in my build logs (when I build the Android app)

Fallback handler could not load library /opt/unity/Editor/Data/MonoBleedingEdge/lib/libCesiumForUnityNative-Runtime.so
Fallback handler could not load library /opt/unity/Editor/Data/MonoBleedingEdge/lib/libCesiumForUnityNative-Runtime
Fallback handler could not load library /opt/unity/Editor/Data/MonoBleedingEdge/lib/libCesiumForUnityNative-Runtime
DllNotFoundException: CesiumForUnityNative-Runtime assembly:<unknown assembly> type:<unknown type> member:(null)
  at (wrapper managed-to-native) Reinterop.ReinteropInitializer.initializeReinterop(ulong,intptr,int)
  at Reinterop.ReinteropInitializer..cctor () [0x03d0e] in ./Reinterop/Reinterop.RoslynSourceGenerator/ReinteropInitializer.cs:947 
Rethrow as TypeInitializationException: The type initializer for 'Reinterop.ReinteropInitializer' threw an exception.
  at CesiumForUnity.CesiumGeoreference.CreateImplementation () [0x00000] in ./Reinterop/Reinterop.RoslynSourceGenerator/CesiumGeoreference-generated.cs:35 
  at CesiumForUnity.CesiumGeoreference..ctor () [0x00072] in ./Reinterop/Reinterop.RoslynSourceGenerator/CesiumGeoreference-generated.cs:52 
...
...
...
Win32Exception: ApplicationName='cmake', CommandLine='-B /root/project/Packages/com.cesium.unity/native~/build-Android-arm64 -S /root/project/Packages/com.cesium.unity/native~ -DEDITOR=false -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="/root/project/Packages/com.cesium.unity/Plugins/Android/arm64" -DREINTEROP_GENERATED_DIRECTORY=generated-Android -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_TOOLCHAIN_FILE="extern/android-toolchain.cmake"', CurrentDirectory='/root/project/Packages/com.cesium.unity/native~', Native error= Cannot find the specified file
  at System.Diagnostics.Process.StartWithCreateProcess (System.Diagnostics.ProcessStartInfo startInfo) [0x002f9] in <d2ad639727fd46a8b6c982e3f40f3172>:0 
  at System.Diagnostics.Process.Start () [0x0003a] in <d2ad639727fd46a8b6c982e3f40f3172>:0 
  at (wrapper remoting-invoke-with-check) System.Diagnostics.Process.Start()
  at CesiumForUnity.CompileCesiumForUnityNative.RunAndLog (System.Diagnostics.ProcessStartInfo startInfo, System.IO.StreamWriter log, System.String logFilename) [0x0003e] in ./Packages/com.cesium.unity/Editor/CompileCesiumForUnityNative.cs:463 
  at CesiumForUnity.CompileCesiumForUnityNative.BuildNativeLibrary (CesiumForUnity.CompileCesiumForUnityNative+LibraryToBuild library) [0x001cd] in ./Packages/com.cesium.unity/Editor/CompileCesiumForUnityNative.cs:421 
  at CesiumForUnity.CompileCesiumForUnityNative.OnPostBuildPlayerScriptDLLs (UnityEditor.Build.Reporting.BuildReport report) [0x00016] in ./Packages/com.cesium.unity/Editor/CompileCesiumForUnityNative.cs:195 
  at UnityEditor.Build.BuildPipelineInterfaces.OnPostBuildPlayerScriptDLLs (UnityEditor.Build.Reporting.BuildReport report) [0x00033] in /home/bokken/build/output/unity/unity/Editor/Mono/BuildPipeline/BuildPipelineInterfaces.cs:603 

One other thing I’ve tried is running CesiumForUnity.BuildCesiumForUnity.CompileForAndroidAndExit at the end of my docker process, before moving over the package. This command is successful (I have to create the Empty Unity scene which I saw was done here https://github.com/CesiumGS/cesium-unity/blob/main/Build~/Package.cs#L49) and in my apk I have a larger libCesiumForUnityNative-Runtime.so file which size is at least much closer to when I build locally, but still throws the same errors at runtime.

Would love any pointers!

Thanks!

Yep, this is complicated. Mostly out of necessity, unfortunately. Since you’re running on Linux and targeting Quest, you need to compile for both Linux and Android. And then the outputs of each need to be combined in the right way so that both are usable.

The best way to do this by far is to imitate what our CI system does:

The critical part is where it runs the scripts on the Build~ directory:

But you’ll need to modify the build scripts for Linux. Specifically this method:

You’ll see a block in there that says if (OperatingSystem.IsWindows()), and inside that block you can see some code that builds the Android player binaries by calling CompileForAndroidAndExit. So you’ll need to do that as part of your Linux build process, too. Simply calling CompileForAndroidAndExit isn’t enough, though. You also need to combine the outputs, which is what the AddGeneratedFiles does.

Thanks, ok yea so that seems to confirm what I expected, it’s the packaging/merging part that I don’t have down yet. The last part of my docker is below, so I’m calling CompileForEditorAndExit, then building, then CompileForAndroidAndExit. Then the part I need to do now is implement the AddGeneratedFiles logic to merge and place these in the right place, before building my project, does that sound right?

Do I also need to generate all these .meta files if I’m doing a headless build? I saw the Package script does that as well.

WORKDIR /cesium-unity-samples/Packages/com.cesium.unity
RUN export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 && dotnet publish Reinterop~ -o .

# Now Open Unity and let it compile
RUN unity-editor -quit -batchmode -nographics -projectPath /cesium-unity-samples -username "$UNITY_USERNAME" -password "$UNITY_PASSWORD" -serial "$UNITY_SERIAL" -executeMethod CesiumForUnity.BuildCesiumForUnity.CompileForEditorAndExit

# Make sure we have generated filess
RUN ls -la /cesium-unity-samples/Packages/com.cesium.unity/native~/Runtime

# Now build cesium-unity
RUN cd /cesium-unity-samples/Packages/com.cesium.unity/native~ && cmake -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo && cmake --build build -j14 --target install --config RelWithDebInfo

# Create empty scene
# https://github.com/CesiumGS/cesium-unity/blob/main/Build~/Package.cs#L47
RUN mkdir /cesium-unity-samples/Assets/Scenes
RUN touch /cesium-unity-samples/Assets/Scenes/Empty.unity
RUN echo "%YAML 1.1" > /cesium-unity-samples/Assets/Scenes/Empty.unity
RUN echo "%TAG !u! tag:unity3d.com,2011:" > /cesium-unity-samples/Assets/Scenes/Empty.unity

# Now build for Android
RUN unity-editor -quit -batchmode -nographics -projectPath /cesium-unity-samples -buildTarget Android -username "$UNITY_USERNAME" -password "$UNITY_PASSWORD" -serial "$UNITY_SERIAL" -executeMethod CesiumForUnity.BuildCesiumForUnity.CompileForAndroidAndExit

Yeah, the .meta files on the native DLL/SO files are essential, because those tell Unity which platforms they apply to.

Ok I’m closer (I think), but I’m still seeing those DllNotFoundException: CesiumForUnityNative-Runtime in the logs when I build for Android on my side which I think is causing the issues when I run it on the quest pro (even though the build is “successful”).

I’ve completely shifted to just using your C# build code, I pushed it up just so you can see the diff

The merge logic was complicated enough I didn’t want to try to reimplement that in bash so I started with those functions but then realized I might as well just use all the logic.

I’m running all this in a docker that just has all the pre-reqs
https://hub.docker.com/repository/docker/rimig/cesium-unity-base/general

I run that docker, checkout code, build reinterop, then run the C# build code.

So it builds the package with PackAndExit and then I copy that tarball to my Packages folder when I go run my Build code for our application apk. The com.cesium.unity tgz that gets generated looks pretty good except for a couple small differences I noticed when comparing to the one you publish

A few things I’m thinking about now:

  1. My com.cesium.unity-1.5.0.tgz doesn’t seem to include the libCesiumForUnityNative-Editor.dylib files in Editor/arm64 or Editor/x86_64, which it seems gets generated by the build for mac steps (but those use those CMAKE flags for Mac architectures)
  2. My resulting .so files seem slightly smaller than yours (within 10MB)
  3. If my final device is a Quest Pro (Android) do I need to have a BuildTarget of StandaloneLinux64 anywhere? Like do you think I need one of these for StandaloneLinux64: https://github.com/CesiumGS/cesium-unity/blob/0baf5cb9c6e947292a362090e05083dc01e52436/Editor/BuildCesiumForUnity.cs#L62

Still feels like I have to be so close…