manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.43k stars 125 forks source link

Question regarding Android support #415

Closed unbiaseduser closed 1 year ago

unbiaseduser commented 1 year ago

So, I recently added Manifold to my Android project following the instructions on http://manifold.systems/android.html. When I built the app, I got this error:

> Task :app:mergeExtDexDebug
AGPBI: {"kind":"error","text":"com.android.tools.r8.internal.k2: MethodHandle.invoke and MethodHandle.invokeExact are only supported starting with Android O (--min-api 26)","sources":[{"file":"C:\\Users\\Admin\\caches\\modules-2\\files-2.1\\systems.manifold\\manifold-util\\2022.1.34\\5c7b3128728394b090bcb2a89db377dfe54592d\\manifold-util-2022.1.34.jar"}],"tool":"D8"}
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeExtDexDebug'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
   > Failed to transform manifold-util-2022.1.34.jar (systems.manifold:manifold-util:2022.1.34) to match attributes {artifactType=android-dex, asm-transformed-variant=NONE, dexing-enable-desugaring=true, dexing-enable-jacoco-instrumentation=false, dexing-is-debuggable=true, dexing-min-sdk=21, org.gradle.category=library, org.gradle.libraryelements=jar, org.gradle.status=release, org.gradle.usage=java-runtime}.
      > Execution failed for DexingWithClasspathTransform: C:\Users\Admin\caches\modules-2\files-2.1\systems.manifold\manifold-util\2022.1.34\5c7b3128728394b090bcb2a89db377dfe54592d\manifold-util-2022.1.34.jar.
         > Error while dexing.

Apparently Manifold depends on Java features that only Android 8.0+ supports. I just want to ask: Is this intended? And if that is indeed the case, I suggest documenting this on your Android docs.

In case you're wondering, here's my build.gradle (I created a brand new project to not include unrelated stuff that might be distracting):

plugins {
    id 'com.android.application'
}

getTasks().withType(JavaCompile) {
    options.compilerArgs += ['-Xplugin:Manifold']
}

android {
    namespace 'com.example.manifoldtest'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.manifoldtest"
        minSdk 21
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    compileOnly 'systems.manifold:manifold:2022.1.34'
    implementation 'systems.manifold:manifold-rt:2022.1.34'
    compileOnly 'systems.manifold:manifold-ext:2022.1.34'
    implementation 'systems.manifold:manifold-ext-rt:2022.1.34'
    compileOnly 'systems.manifold:manifold-strings:2022.1.34'
    compileOnly 'systems.manifold:manifold-exceptions:2022.1.34'

    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}
rsmckinney commented 1 year ago

@unbiaseduser Hi. The android docs could use some help, sorry for the confusion. It looks like the manifold core and util modules use MethodHandle, which appears to be the issue here. MethodHandle usage is fairly new, probably within a year or two. My bad, as I had assumed staying within the bounds of JDK 8 would keep manifold on target with android. As evidenced here my testing doesn't have good coverage for older versions of android as it should.

At the moment I don't see an alternative to one particular usage of MethodHandle#invokeExact. I'll see what I can do.

What version of android are you targeting?

unbiaseduser commented 1 year ago

I'm targeting Android 5.0, as it's a good default (Android Studio selects that by default for new projects, and Android libraries' min SDK don't go higher than that (afaik)).

rsmckinney commented 1 year ago

@unbiaseduser I'm looking into a workaround for usage of MethodHandle. In any case, looking at the statistics for current Android API usage, Android 8 (level 26) covers 92.5% of all devices in use. Maybe that is good enough? shrug

rsmckinney commented 1 year ago

I think I'll have a MethodHandle-free build by EOD. I'll update this issue when that happens.

rsmckinney commented 1 year ago

Manifold release 2022.1.37 removes the MethodHandle invoke & invokeExact usage. Hopefully, you can use Android 5 with your project now.

Update: this release is not free of MethodHandle usage, however it did remove usage of MethodHandle.invoke/invokeExact as the message indicates. I have my doubts now about this working for you. I'm looking into remove all MethodHandle usage, but it doesn't look feasible at the moment.

unbiaseduser commented 1 year ago

Thanks very much, it works well now! Yeah, Android fragmentation sucks. Even if only 7.5% devices run low Android versions, that's still not negligible, and worst of all, ppl who use 5+ year old devices are very unlikely to be able to upgrade (afaik). That along with the fact that supporting old Android versions actually has little overhead for most projects, explains why pretty much 97% FOSS Android projects still have support for them. Life sure is a series of tradeoffs.

Speaking of tradeoffs, I played around with my sample project and noticed that while building with JDK 11 (the one bundled with Android Studio) everything's fine but as soon as I switched to a higher version JDK (via Build, Execution, Deployment > Build Tools > Gradle > Gradle JDK) I was instantly met with this:

> Task :app:compileDebugJavaWithJavac
java/lang/Object.java:6: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()
                             ^
java/lang/Object.java:10: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()
                             ^
java/lang/Object.java:14: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()
                             ^
java/lang/Object.java:21: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()
                             ^
java/lang/Object.java:28: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()
                             ^
java/lang/Object.java:32: error: package jdk.internal.vm.annotation does not exist
  @jdk.internal.vm.annotation.IntrinsicCandidate()

So it's either Manifold or Java 19 (which I'm currently using for my real project). Strangely, this doesn't happen in a sample regular Java project (not Android), even when building with JDK 19, so I guess the problem lies in Google's tooling. Tradeoffs...

*By "building with" I mean setting language level to that JDK version as in

compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
rsmckinney commented 1 year ago

Thanks for reporting this.

Manifold supports all Java SE LTS releases plus latest, which currently includes JDK 20. But for Android I test only with Java 8 and only with a couple of configs. I need to improve on that.