Search code examples
javaandroidkotlinokhttpshadowjar

Kotlin properties in a package relocated with Shadow do not work


I'm trying to relocate a package (OkHttp 4, to be specific) with Shadow, with the following Gradle config:

apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
    archiveBaseName.set('my_archive')
    archiveClassifier.set(null)
    version = null

    relocate 'okhttp3', 'my.prefix.okhttp3'
    relocate 'okio', 'my.prefix.okio'
}

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.2.1") {
        exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
    }
}

(I've omitted the buildscript part, the important bit there is that the version of Shadow used is 5.1.0. Package prefixes etc have also been changed)

This worked before, with OkHttp 3.12.0 and earlier, which was purely Java. Now that OkHttp 4 is written in Kotlin, I'm having trouble using properties, in Kotlin code specifically. When used from Java, the relocated OkHttp works just fine. But accessing properties in Kotlin, like this:

val cache = httpClient.cache

... results in an exception:

java.lang.NoSuchMethodError: No virtual method getCache()Lmy/prefix/okhttp3/Cache; in class Lmy/prefix/okhttp3/OkHttpClient; or its super classes (declaration of 'my.prefix.okhttp3.OkHttpClient' appears in /data/app/redacted.redacted-0yalPGR5aw0RSY2Zdxnq7Q==/base.apk)

As you can see, the app is an Android app, in case that matters.

Any ideas what could be wrong with my config?


Solution

  • This is not a perfect solution, but makes things work at least somewhat. Instead of excluding the embedded Kotlin stdlib entirely, let it be and also relocate it. It's easiest by relocating everything to the same prefix, by adding this to build.gradle:

    task relocateShadowJar(type: ConfigureShadowRelocation) {
        target = tasks.shadowJar
        prefix = "my.prefix"
    
    }
    
    tasks.shadowJar.dependsOn tasks.relocateShadowJar
    

    You may also need to exclude some Kotlin META-INF bits in the shadowJar task.

    The problem (at least for me) with this approach is that while everything more or less works, you now likely have the Kotlin stdlib twice in your app: your prefixed version and the one you use normally. Also, property syntax doesn't work. So as in the example in the question,

    val cache = httpClient.cache
    

    actually becomes

    val cache = httpClient.cache()
    

    ... which isn't great.

    Another problem for me now is that in Android Studio, everything in the resulting jar shows as red in the code editor, i.e. Android Studio doesn't see what's in the jar. Everything compiles just fine, it's just that editing code itself is a pain, since you don't get any code completion and Android Studio sometimes messes with your imports. (since it obviously thinks that these imports are invalid)