Search code examples
kotlinlibcurlkotlin-nativecinterop

How can I build a libcurl Kotlin Native application on Windows?


I'm relatively new to Kotlin Native, and to learn more about it, I am studying these two tutorials, one from the official JetBrains documentation and the other from the jonnyzzz blog, both focused on creating an application using C Interop and libcurl:

  1. https://kotlinlang.org/docs/native-app-with-c-and-libcurl.html

  2. https://jonnyzzz.com/blog/2018/10/29/kn-libcurl-windows/

I'm trying to build the above application on Windows 11.

After struggling a little, I finally managed to import the libcurl library in my Kotlin Native project, making it possible to call the relative C functions. So far, so good, but when I try to build the entire project, these are the errors I receive:

e: C:\Users\Nicola\.konan\dependencies\llvm-11.1.0-windows-x64-essentials/bin/clang++ invocation reported errors

The C:\Users\Nicola\.konan\dependencies\llvm-11.1.0-windows-x64-essentials/bin/clang++ command returned non-zero exit code: 1.
output:
lld-link: error: undefined symbol: __declspec(dllimport) curl_easy_strerror
>>> referenced by C:\Users\Nicola\IdeaProjects\SimpleHttpClient\src\nativeMain\kotlin\Main.kt:15
>>>               C:\Users\Nicola\AppData\Local\Temp\konan_temp9458678904101419787\result.o:(libcurl_curl_easy_strerror_wrapper33)

lld-link: error: undefined symbol: __declspec(dllimport) curl_easy_init
>>> referenced by C:\Users\Nicola\IdeaProjects\SimpleHttpClient\src\nativeMain\kotlin\Main.kt:15
>>>               C:\Users\Nicola\AppData\Local\Temp\konan_temp9458678904101419787\result.o:(libcurl_curl_easy_init_wrapper36)

lld-link: error: undefined symbol: __declspec(dllimport) curl_easy_perform
>>> referenced by C:\Users\Nicola\IdeaProjects\SimpleHttpClient\src\nativeMain\kotlin\Main.kt:15
>>>               C:\Users\Nicola\AppData\Local\Temp\konan_temp9458678904101419787\result.o:(libcurl_curl_easy_perform_wrapper37)

lld-link: error: undefined symbol: __declspec(dllimport) curl_easy_cleanup
>>> referenced by C:\Users\Nicola\IdeaProjects\SimpleHttpClient\src\nativeMain\kotlin\Main.kt:15
>>>               C:\Users\Nicola\AppData\Local\Temp\konan_temp9458678904101419787\result.o:(libcurl_curl_easy_cleanup_wrapper38)

lld-link: error: undefined symbol: curl_easy_setopt
>>> referenced by C:\Users\Nicola\AppData\Local\Temp\konan_temp9458678904101419787\result.o:(knifunptr_libcurl39_curl_easy_setopt)
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

FAILURE: Build failed with an exception.

* What went wrong:
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.

* Get more help at https://help.gradle.org

BUILD FAILED in 8s

And this is the code I wrote:

Main.kt:

import libcurl.*
import kotlinx.cinterop.*

fun main(args: Array<String>) {
    val curl = curl_easy_init()
    if (curl != null) {
        curl_easy_setopt(curl, CURLOPT_URL, "http://jonnyzzz.com")
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
        val res = curl_easy_perform(curl)
        if (res != CURLE_OK) {
            println("curl_easy_perform() failed ${curl_easy_strerror(res)?.toKString()}")
        }
        curl_easy_cleanup(curl)
    }
}

libcurl.def:

headers = curl/curl.h
headerFilter = curl/*

build.gradle.kts:

plugins {
    kotlin("multiplatform") version "1.7.10"
}

group = "me.nicola"
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 {
        compilations.getByName("main") {
            cinterops {
                val libcurl by creating {
                    val includePath = "C:\\Users\\Nicola\\Documents\\curl-7.87.0\\curl-7.87.0\\include"

                    defFile(project.file("src/nativeInterop/cinterop/libcurl.def"))
                    packageName("libcurl")
                    compilerOpts("-I/$includePath")
                    includeDirs.allHeaders(includePath)
                }
            }
        }
        binaries {
            executable {
                val buildPath = "C:\\Users\\Nicola\\Documents\\curl-7.87.0\\curl-7.87.0\\builds\\libcurl-vc-x86-release-dll-ipv6-sspi-schannel\\lib\\libcurl.lib"

                entryPoint = "main"
                linkerOpts(buildPath)
            }
        }
    }
    sourceSets {
        val nativeMain by getting
        val nativeTest by getting
    }
}

What am I missing?

Thanks a lot for your precious time.


Solution

  • There's a more complete demonstration of how to link Curl in the Ktor project - although it probably contains a lot more configuration than you need. The important part is

    # libcurl.def 
    
    linkerOpts.mingw_x64 =       -lcurl \
                                 -L/usr/lib64 \
                                 -L/usr/lib/x86_64-linux-gnu \
                                 -L/opt/local/lib \
                                 -L/usr/local/opt/curl/lib \
                                 -L/opt/homebrew/opt/curl/lib \
                                 -LC:/msys64/mingw64/lib \
                                 -LC:/Tools/msys64/mingw64/lib \
                                 -LC:/Tools/msys2/mingw64/lib
    

    This says "this program needs the library libcurl.a." Where will it look for that file? On any path that is provided by -L, so you'll also have to add -L/dir/that/contains/libraries to your .def file.

    Alternatively, you could statically link Curl, and then the Kotlin/Native compiler would automatically link the library.

    # libcurl.def 
    
    staticLibraries = libcurl.a
    
    # -lcurl is not needed
    
    linkerOpts.mingw_x64 =       -L/usr/lib64 \
                                 -L/usr/lib/x86_64-linux-gnu \
                                 -L/opt/local/lib \
                                 -L/usr/local/opt/curl/lib \
                                 -L/opt/homebrew/opt/curl/lib \
                                 -LC:/msys64/mingw64/lib \
                                 -LC:/Tools/msys64/mingw64/lib \
                                 -LC:/Tools/msys2/mingw64/lib
    

    Note that it's also possible to get link errors if libcurl.a is compiled for the wrong platform, or if it's compiled using an incompatible version of gcc or libc (because Kotlin/Native uses old versions of gcc and libc).