Search code examples
androidc++11boostboost-regexndk-build

Error linking Boost::regex in Android Project (undefined reference) with crystax ndk


I am currently trying to use the boost::regex library in my Android native lib on Android Studio. But I get this two linker errors:

F:\Tools\dev\Android\ndks\crystax-ndk-10.3.2/sources/boost/1.64.0/include/boost/regex/v4/regex_search.hpp:56: error: undefined reference to 'boost::re_detail_106400::perl_matcher<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<boost::sub_match<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::find()'

F:\Tools\dev\Android\ndks\crystax-ndk-10.3.2/sources/boost/1.64.0/include/boost/regex/v4/perl_matcher.hpp:383: error: undefined reference to 'boost::re_detail_106400::perl_matcher<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<boost::sub_match<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::construct_init(boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags)'

I am using the crystax-ndk (10.3.2) to compile my native c++ code. The boost library is version 1.64.0 and I use the precompiled static libs from here.

My build.gradle looks like this:

import org.gradle.internal.os.OperatingSystem

apply plugin: 'com.android.model.application'

final APP_ABIS = ["armeabi", "armeabi-v7a", "x86"]
final BOOST_STATIC_LIBS = ["boost_system", "boost_filesystem", "boost_thread", "boost_regex", "boost_iostreams", "boost_log_setup", "boost_log"]
final OPENSSL_SHARED_LIBS = ["ssl", "crypto"]

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "25.0.2"

        defaultConfig.with {
            applicationId = "de.linux13524.youtube_list_downloader"
            minSdkVersion.apiLevel = 17
            targetSdkVersion.apiLevel = compileSdkVersion as Integer
            versionCode = 1
            versionName = "1.0"
        }
    }

    android.ndk {
        toolchain = "gcc"
        toolchainVersion = "5"
        moduleName = "native-lib"
        cppFlags.add("-std=c++11")
        cppFlags.add("-fexceptions")
        cppFlags.add("-frtti")
        cppFlags.add("-DANDROID")
        cppFlags.add("-I" + getBoostIncDir())
        cppFlags.add("-I" + getOpenSSLIncDir())
        cppFlags.add("-I" + file("../../lib").absolutePath)
        cppFlags.add("-I" + file("../../lib/nowide_standalone").absolutePath)
        cppFlags.add("-I" + file("../../include").absolutePath)
        ldLibs.addAll BOOST_STATIC_LIBS
        ldLibs.addAll OPENSSL_SHARED_LIBS
        ldLibs.add("log")
        stl = "gnustl_shared"
    }

    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file('proguard-rules.pro'))
        }
    }

    android.productFlavors {
        APP_ABIS.each { abi ->
            create(getFlavorName(abi)) {
                ndk.with {
                    abiFilters.add(abi)
                    getPrebuiltLibPaths(abi).each { path ->
                        ldFlags.add("-L" + path)
                    }
                }
            }
        }
    }

    android.sources {
        main {
            jni {
                source {
                    srcDirs = ["/src/main/jni".toString(),
                               "../../src".toString(),
                               "../../include".toString(),
                               "../../lib".toString()]
                }
            }
        }
    }
}

tasks.all {
    task ->
        if (task.name.startsWith('link')) {
            task.dependsOn copyNativeLibs, stripNativeLibs
        }
}

task copyNativeLibs {
    ["debug", "release"].each { buildType ->
        APP_ABIS.each { abi ->
            def libs = [:]
            BOOST_STATIC_LIBS.each { name ->
                libs[name] = "${getBoostLibDir(abi)}/lib${name}.a"
            }
            OPENSSL_SHARED_LIBS.each { name ->
                libs[name] = "${getOpenSSLLibDir(abi)}/lib${name}.so"
            }
            libs.crystax = getLibCrystax(abi)

            libs.each { name, file ->
                dependsOn tasks.create(name: "copy-native-library-${name}-${abi}-${buildType}", type: Copy) {
                    from file
                    into getTargetLibDir(abi, buildType)
                }
            }
        }
    }
}

task stripNativeLibs(dependsOn: copyNativeLibs) {
    ["debug", "release"].each { buildType ->
        APP_ABIS.each { abi ->
            def libs_static = []
            def libs_dynamic = []
            libs_static.addAll(BOOST_STATIC_LIBS)
            libs_dynamic.addAll(OPENSSL_SHARED_LIBS)
            libs_dynamic.add("crystax")

            libs_static.each { name ->
                dependsOn tasks.create(name: "strip-native-library-${name}-${abi}-${buildType}", type: Exec) {
                    commandLine getStripExecutable(abi), "--strip-unneeded", "${getTargetLibDir(abi, buildType)}/lib${name}.a"
                }
            }

            libs_dynamic.each { name ->
                dependsOn tasks.create(name: "strip-native-library-${name}-${abi}-${buildType}", type: Exec) {
                    commandLine getStripExecutable(abi), "--strip-unneeded", "${getTargetLibDir(abi, buildType)}/lib${name}.so"
                }
            }
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}

def getNdkDir() {
    if (System.env.ANDROID_NDK_ROOT != null)
        return System.env.ANDROID_NDK_ROOT

    Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    def ndkdir = properties.getProperty('ndk.dir', null)
    if (ndkdir == null)
        throw new GradleException("""\
                NDK location not found.
                Define location with ndk.dir in the local.properties file
                or with an ANDROID_NDK_ROOT environment variable.""")

    return ndkdir
}

def getCrystaxNdkDir() {
    def ndkDir = getNdkDir()
    if (!(new File(ndkDir, "sources/crystax").exists()))
        throw new GradleException("""\
            '${ndkDir}' is not a CrystaX NDK.
            Edit ndk.dir in local.properties or set ANDROID_NDK_ROOT
            environment variable pointing to CrystaX NDK""")

    return ndkDir
}

static def getFlavorName(abi) {
    switch (abi) {
        case "armeabi":
            return "arm"
        case "armeabi-v7a":
            return "arm7"
        case "arm64-v8a":
            return "arm64"
        default:
            return abi.replaceAll('-', '_')
    }
}

static def getToolchainName(abi) {
    switch (abi) {
        case ~/^armeabi.*/:
            return "arm-linux-androideabi"
        case ~/^arm64.*/:
            return "aarch64-linux-android"
        case "mips":
            return "mipsel-linux-android"
        case "mips64":
            return "mips64el-linux-android"
        case ["x86", "x86_64"]:
            return abi
        default:
            throw new GradleException("Unsupported ABI: '${abi}'")
    }
}

static def getToolchainPrefix(abi) {
    switch (abi) {
        case ~/^armeabi.*/:
            return "arm-linux-androideabi"
        case ~/^arm64.*/:
            return "aarch64-linux-android"
        case "mips":
            return "mipsel-linux-android"
        case "mips64":
            return "mips64el-linux-android"
        case "x86":
            return "i686-linux-android"
        case "x86_64":
            return "x86_64-linux-android"
        default:
            throw new GradleException("Unsupported ABI: '${abi}'")
    }
}

static def getHostOS() {
    if (OperatingSystem.current().isLinux())
        return "linux"
    if (OperatingSystem.current().isMacOsX())
        return "darwin"
    if (OperatingSystem.current().isWindows())
        return "windows"
    throw new GradleException("Unsupported host OS")
}

static def getHostArch() {
    def arch = System.getProperty("os.arch")
    switch (arch) {
        case ["x86_64", "amd64"]:
            return "x86_64"
        case ~/^i[3456]86/:
        case "x86":
            return "x86"
        default:
            throw new GradleException("Can't detect host's CPU architecture: '${arch}'")
    }
}

static def getHostTag() {
    def tag = getHostOS()
    def arch = getHostArch()
    if (tag != "windows" || arch != "x86")
        tag += "-${arch}"
    return tag
}

def getStripExecutable(abi) {
    def ndk = getCrystaxNdkDir()
    def toolchainName = getToolchainName(abi)
    def toolchainPrefix = getToolchainPrefix(abi)
    def hostTag = getHostTag()
    def strip = "${ndk}/toolchains/${toolchainName}-5/prebuilt/${hostTag}/bin/${toolchainPrefix}-strip"
    if (OperatingSystem.current().isWindows())
        strip = strip.replaceAll('/', '\\\\') + '.exe'
    return strip
}

def getPrebuiltLibPaths(abi) {
    def paths = []
    paths += getBoostLibDir(abi)
    paths += getOpenSSLLibDir(abi)
    paths += getLibCrystaxDir(abi)
    return paths
}

def getTargetLibDir(abi, buildType) {
    return "${buildDir}/intermediates/binaries/${buildType}/${getFlavorName(abi)}/lib/${abi}"
}

def getLibCrystaxDir(abi) {
    return "${getCrystaxNdkDir()}/sources/crystax/libs/${abi}"
}

def getLibCrystax(abi) {
    return "${getLibCrystaxDir(abi)}/libcrystax.so"
}

def getBoostDir() {
    return "${getCrystaxNdkDir()}/sources/boost/1.64.0"
}

def getBoostIncDir() {
    return "${getBoostDir()}/include"
}

def getBoostLibDir(abi) {
    return "${getBoostDir()}/${abi}/lib"
}

def getOpenSSLDir() {
    return "${getCrystaxNdkDir()}/sources/openssl/1.0.2h"
}

def getOpenSSLIncDir() {
    return "${getOpenSSLDir()}/include"
}

def getOpenSSLLibDir(abi) {
    return "${getOpenSSLDir()}/libs/${abi}"
}

I added boost_regex to the list of libraries to link (BOOST_STATIC_LIBS). Beside of the regex part (regex_search), I'm using the asio part for http, the filesystem part and the logging part without any (linker) problems.

When I examined the libboost_regex.a library with nm --demangle libboost_regex.a | grep perl_matcher | grep regex_traits | grep "find()", the "undefined reference" is shown in the list of results. (The other reference is also in the list)

So what can be the problem here? Am I missing something important?


Solution

  • I fixed my problem by changing from crystax to Android NDK r16b with CMake and setting the std lib to c++11.

    In the build.gradle:

    ...
    externalNativeBuild {
        cmake {
            cppFlags "-std=c++11 -frtti -fexceptions"
            arguments "-DANDROID_STL=c++_static"
            }
        }
    ...
    

    Now the Boost libraries (updated to version 1.66) link without problems.