Search code examples
kotlinsdl-2kotlin-native

Unable to link SDL2 in kotlin native


I am unable to link a Kotlin-native binary with SDL2. Namely undefined references.

I come from a c/c++/other native languages background. So I know exactly what it is failing to do, but I have no idea how to fix it.

I'm on Linux and first trying to get it working for such. Then adapt it to other platforms later.

The sources have been adapted from https://github.com/JetBrains/kotlin/tree/master/kotlin-native/samples/tetris

libsdl.def:
headers = SDL.h stdlib.h time.h
entryPoint = SDL_main

headerFilter = SDL* stdlib.h time.h

compilerOpts = -D_POSIX_SOURCE
compilerOpts.linux = -I/usr/include/SDL2 -D_REENTRANT
linkerOpts.linux = -L/usr/lib -pthread -lSDL2 -lm -ldl
build.gradle.kts:
plugins {
    kotlin("multiplatform") version "1.6.10"
}

group = "me.ketanr"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

kotlin {
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }

    nativeTarget.apply {
        binaries {
            executable {
                entryPoint = "main"

                linkerOpts("-L/usr/lib", "-L/usr/lib/x86_64-linux-gnu", "-lSDL2", "-lpthread", "-ldl", "-lm")
            }
        }

        // compilations.getByName("main") {
        //     cinterops {
        //         val libsdl by creating {
        //             includeDirs("/usr/include", "/usr/include/x86_64-linux-gnu", "/usr/include/SDL2")
        //         }
        //     }
        // }
        compilations["main"].cinterops {
            val libsdl by creating {
                includeDirs("/usr/include", "/usr/include/x86_64-linux-gnu", "/usr/include/SDL2")
            }
        }
    }
    sourceSets {
        val nativeMain by getting
        val nativeTest by getting
    }
}
error log:
> Task :linkDebugExecutableNative FAILED
e: /home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/bin/ld.gold invocation reported errors

The /home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/bin/ld.gold command returned non-zero exit code: 1.
output:
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/crt1.o
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/crti.o
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/../../lib/gcc/x86_64-unknown-linux-gnu/8.3.0/crtbegin.o
/tmp/konan_temp6274769675767169924/result.o
/usr/lib/libSDL2.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib64/libpthread.so.0
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libdl.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libm.so
/usr/lib/libSDL2.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libm.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libdl.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_personality.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(del_op.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(si_class_type_info.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_alloc.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_exception.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_ptr.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(class_type_info.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(del_ops.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_catch.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_throw.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(guard.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_terminate.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_term_handler.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_globals.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(vterminate.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_call.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_unex_handler.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(list.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(tree.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(chrono.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(hashtable_c++0x.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(tinfo.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(eh_type.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(pure.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libstdc++.a(guard_error.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libdl.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/libm.so
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib64/libpthread.so.0
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/../../lib/gcc/x86_64-unknown-linux-gnu/8.3.0/libgcc.a(cpuinfo.o)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libgcc_s.so.1
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib64/libc.so.6
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib64/libc_nonshared.a(elf-init.oS)
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib64/ld-linux-x86-64.so.2
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/lib/libgcc_s.so.1
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/../../lib/gcc/x86_64-unknown-linux-gnu/8.3.0/crtend.o
/home/ketanr/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/sysroot/usr/lib/crtn.o
/usr/lib/libSDL2.so: error: undefined reference to 'powf', version 'GLIBC_2.27'
/usr/lib/libSDL2.so: error: undefined reference to 'exp', version 'GLIBC_2.29'
/usr/lib/libSDL2.so: error: undefined reference to 'fstat', version 'GLIBC_2.33'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_sigmask', version 'GLIBC_2.32'
/usr/lib/libSDL2.so: error: undefined reference to 'dlerror', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_once', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_setspecific', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_key_create', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_detach', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_join', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_getspecific', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_trywait', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_mutexattr_settype', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'dlopen', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_timedwait', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_post', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_attr_setstacksize', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_init', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'expf', version 'GLIBC_2.27'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_create', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'log', version 'GLIBC_2.29'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_mutexattr_init', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'logf', version 'GLIBC_2.27'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_wait', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_getvalue', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'sem_destroy', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'dlclose', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'dlsym', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'pow', version 'GLIBC_2.29'
/usr/lib/libSDL2.so: error: undefined reference to 'pthread_mutex_trylock', version 'GLIBC_2.34'
/usr/lib/libSDL2.so: error: undefined reference to 'stat', version 'GLIBC_2.33'

Execution failed for task ':linkDebugExecutableNative'.
> Compilation finished with errors

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
uname -a:

Linux fujitsu-endeavour 5.16.10-arch1-1 #1 SMP PREEMPT Wed, 16 Feb 2022 19:35:18 +0000 x86_64 GNU/Linux

cat /etc/*-release:
Arch Linux release
DISTRIB_ID=EndeavourOS
DISTRIB_RELEASE="rolling"
DISTRIB_DESCRIPTION="EndeavourOS Linux"
DISTRIB_CODENAME=rolling
NAME=EndeavourOS
PRETTY_NAME=EndeavourOS
ID=endeavouros
ID_LIKE=arch
BUILD_ID=2021.11.30
ANSI_COLOR="38;2;23;147;209"
HOME_URL='https://endeavouros.com'
DOCUMENTATION_URL='https://forum.endeavouros.com/c/Arch-based-related-questions/bug-reports'
SUPPORT_URL='https://forum.endeavouros.com'
BUG_REPORT_URL='https://forum.endeavouros.com/c/arch-based-related-questions/bug-reports'
LOGO=endeavouros

Solution

  • By default kotlin native uses its own minimal toolchain via konan with a gcc at version 8.3.0, glibc at 2.19, and the Linux kernel at 4.9.

    The recommended way would probably be:

    • Set up a Docker image of an older Linux distro with the same/similar specifications to the konan toolchain, at the very least matching the glibc version.
    • Install SDL2 by package manager so that you now have a version of SDL linked against the version of glibc konan uses.
    • Build the executable in the Docker container.

    Setting up a build system with this old of a glibc version seems improbable because even Debian Stretch doesn't have one quite that old. (2.24) I think the below hacking around to use system libraries might have better luck being used in the Docker container than trying to duplicate the konan toolchain.

    My way of "fixing" this was to override some of the values konan uses in my Gradle build. You can see all of the properties and their default values at kotlin-native/konan/konan.properties in the kotlin-native sources which may be required beyond just initializing SDL2 as that was my only goal with the following and more problems might occur later.

    Keep in mind this is breaking basically every rule they gave on how to use their software that I saw while looking through the bug reports that gave me this information, so anything can break at any time.

    To get my application using SDL2 to build I did the following:

    build.gradle.kts: I overrode the targetSysRoot and libGcc konan properties with my own system. Version for libGcc is set to null here because I have a bash script that replaces this line with my currently installed version since on a rolling release this can change often. It would also be helpful when running on an older system to get better backwards compatibility for release.

        linuxX64 {
            compilations.getByName("main") {
                cinterops {
                    val sdl by creating
                }
            }
            binaries {
                executable {
                    val sysRoot = "/"
                    val libGccVersion = null
                    val libGcc = "/lib/gcc/x86_64-pc-linux-gnu/$libGccVersion"
                    val overriddenProperties = "targetSysRoot.linux_x64=$sysRoot;libGcc.linux_x64=$libGcc"
                    freeCompilerArgs += listOf(
                        "-Xoverride-konan-properties=${overriddenProperties}"
                    )
                    entryPoint = "main"
                }
            }
        }
    

    replaceLibGccVersion.sh: This is the bash file I'm currently using to easily update the libGcc version in build.gradle.kts when needed.

    #!/usr/bin/env bash
    
    buildFile="../build.gradle.kts"
    test -f "$buildFile" || `echo "Build file $buildFile not found. Exiting..." && exit`
    varDef="val libGccVersion = "
    gccVersion="`ls /lib/gcc/x86_64-pc-linux-gnu/`"
    newVersion="$varDef\"$gccVersion\""
    currentVersion=`grep "$varDef" "$buildFile"`
    currentVersion=${currentVersion#"${currentVersion%%[![:space:]]*}"}
    if [ "$newVersion" != "$currentVersion" ]
    then
      echo "Replacing $currentVersion with $newVersion in $buildFile"
      sed -i "s/^\(\s*\)$varDef.*$/\1$newVersion/" "$buildFile"
    else
      echo "$buildFile already has the current libGcc version set"
    fi
    
    

    sdl.def: I simplified because if we're using the local system toolchain, there's no reason to fear mixing between the local system and the build toolchain's header files

    headers = SDL2/SDL.h
    entryPoint = SDL_main
    
    compilerOpts.linux = -I/usr/include
    linkerOpts.linux = -lSDL2
    

    With this I was able to build the usual SDL Hello World window to work.

    Main.kt

    import cnames.structs.SDL_Window
    import kotlinx.cinterop.*
    import sdl.*
    
    private const val SCREEN_WIDTH = 640
    private const val SCREEN_HEIGHT = 480
    
    fun main() {
        if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
            throw Error("SDL could not initialize! SDL_Error: ${SDL_GetError()}")
        }
    
        val window: CPointer<SDL_Window> = SDL_CreateWindow(
            "Hello, world!",
            SDL_WINDOWPOS_UNDEFINED.toInt(), SDL_WINDOWPOS_UNDEFINED.toInt(),
            SCREEN_WIDTH, SCREEN_HEIGHT,
            SDL_WINDOW_SHOWN
        ) ?: throw Error("SDL could not create window! SDL_Error: ${SDL_GetError()}")
    
        val surface: CPointer<SDL_Surface>? = SDL_GetWindowSurface(window)
        SDL_FillRect(surface, null, SDL_MapRGB(surface?.pointed?.format, 0xFF, 0xFF, 0xFF))
        SDL_UpdateWindowSurface(window)
        SDL_Delay(2000)
        SDL_DestroyWindow(window)
        SDL_Quit()
    }