4

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?

Johan Halin
  • 701
  • 7
  • 10

2 Answers2

0

We experience something similar in a non-android project. The package-level "things" (an extension function in our case) are in fact static on a compiler-generated class (in our case its called ExtensionKt.class)

I have no problem accessing that class from Java or Scala code. In Kotlin code however the compiler does not let me to access the ExtensionKt class and the package relocating by the shadow Jar somehow breaks the original syntax so it is not possible to access it via the package name anymore. As a temporary workaround we access the package scoped things from Java:

So instead this Kotlin code:

import my.package.name.niceExtensionFunction

...

val x = SomeClass()
val y = x.niceExtensionFunction()

We do this in Java:

import my.package.name.ExtensionKt;

...

SomeClass x = new SomeClass;
Object y = ExtensionKt.niceExtensionFunction(x)

0

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)

Johan Halin
  • 701
  • 7
  • 10