Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.9.3 incompatible with AGP 7.4.1 #118

Open
Leojangh opened this issue Feb 10, 2023 · 18 comments
Open

0.9.3 incompatible with AGP 7.4.1 #118

Leojangh opened this issue Feb 10, 2023 · 18 comments

Comments

@Leojangh
Copy link

It can't package compiled rust jni libs to aar.So java.lang.UnsatisfiedLinkError not found ocurrs. AGP 7.3.1 has no problem.

@ncalexan
Copy link
Member

Thanks for the report. The limited testing that we do should be able to also test AGP 7.4.x around

// Maps supported Android plugin versions to the versions of Gradle that support it
def supportedVersions = [
"7.0.0": ["7.1.1"],
"4.2.2": ["6.8.3", "7.1.1"],
"4.1.3": ["6.5.1", "6.8.3"],
"4.0.2": ["6.1.1", "6.8.3"],
"3.6.4": ["5.6.4", "6.8.3"],
"3.5.4": ["5.4.1", "5.6.4", "6.8.3"],
"3.1.2": ["4.10.2"]
]
. I'd love a patch to at least try newer versions of Gradle and AGP to see if we can get failing tests, and eventually to fix them. So: help wanted!

Thanks for the report!

@ghost
Copy link

ghost commented Feb 14, 2023

Seconding the breakage. Not sure if I'll be able to spend time debugging it any time soon though.

@bqbs
Copy link

bqbs commented Feb 27, 2023

It's working for me under AGP 7.4.1 and 8.0.0-beta03 both.But I'm just doing some test..so my code are in the same source set.

src
├── main
   ├── cpp
   ├── java
   └── rust

In case you need.Here is some different.
Cargo.toml

...
[lib]
crate_type = ["lib", "cdylib"]

and build.gradle

/*
tasks.whenTaskAdded { task ->
    if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
        task.dependsOn 'cargoBuild'
    }
}
*/

tasks.whenTaskAdded { task ->
    if ((task.name == 'mergeDebugJniLibFolders' || task.name == 'mergeReleaseJniLibFolders')) {
        task.dependsOn 'cargoBuild'
    }
}

@ghost
Copy link

ghost commented Feb 28, 2023

Yeah, it seems like the problem went away for me in the past few days. I'm sending in two PRs to add 7.4.2 to the tests.

@Leojangh
Copy link
Author

Leojangh commented Mar 2, 2023

OK,It repreduces in Gradle 8.0-rc02 and AGP 7.4.1. It seems like the problem went away after Bumping Gradle to 8.0.1.
I found that the compiled rust lib(librust.so) is about 4.2MB in dir build/rustJniLibs/android/arm64-v8a,but the aar contains two different librust.so,one is in aar's assets/arm64-v8a/librust.so which is 980KB and the other is in jni/arm64-v8a/librust.so which is 132KB.
Why there is two .so file in aar? Is it normal?

@ghost
Copy link

ghost commented Mar 10, 2023

I've been getting it again, I think there's some caching interfering with whether or not it works. I can get it to work under 7.4.2 sometimes, but if I wipe caches and try again then I'll get the problem again. Trying to dig in a bit more. Gradle 7.6 and AGP 7.4.2.

@ghost
Copy link

ghost commented Mar 11, 2023

Some more detail. EDIT: Lots more detail, I'm just just adding updates while I wait for builds to run. I'm checking for the bug, where the JNI doesn't make it onto the device and we get java.lang.UnsatisfiedLinkError: dlopen failed: library "lib<LIB_NAME>.so" not found. I'm doing this by running the connectedAndroidTest task for my test app (Validation), which has an implementation dependency on the Android library that builds and includes the Rust JNI library.

If I switch my AGP version from 7.3.1 to 7.4.2, do a clean and a re-build, then the build/outputs/apk/debug/validation-debug.apk file is missing it's lib directory and the the test fails with the "library not found" error. But I just got it to work with 7.4.2, I added android.sourceSets.getByName("debug") { jniLibs.srcDir("$buildDir/rustJniLibs/android") } to my Android Library's build.gradle.kts file, re-ran connectedAndroidTest and the test passed, with the JNI library being in the debug.apk file's lib directory.

I did some rebuilds, including clearing Android Studio's cache, and it seemed to be working. At this point I had this in my build.gradle.kts:

cargo {
    module = "../crates/mylib"
    targetDirectory = "../target"
    targetIncludes = arrayOf("libmylib.so")
    libname = "mylib"
    targets =
        listOf(
            "arm", // Older Physical Android Devices
            "arm64", // Recent Physical Android Devices
            "x86", // Older Emulated devices, including the ATD Android Test device
            "x86_64", // Most Emulated Android Devices
        )
    features { defaultAnd(arrayOf("android")) }
}

android.sourceSets.getByName("androidTest") { jniLibs.srcDir("$buildDir/rustJniLibs/android") }
android.sourceSets.getByName("debug") { jniLibs.srcDir("$buildDir/rustJniLibs/android") }

// Must manually configure that Android build to depend on the JNI artifacts
tasks.withType<com.android.build.gradle.tasks.MergeSourceSetFolders>().configureEach {
    if (this.name.contains("Jni")) {
        this.dependsOn(tasks.named("cargoBuild"))
    }
}
afterEvaluate { tasks.named("preBuild").dependsOn(tasks.named("cargoBuild")) }

So so far so good?

I commented out the line that added it to the debug source set and the test failed, then added it back and it worked again. Then I tried launching the debug build of the app and that worked, then ran the "release" build of the app, and it the app crashed, library not found. Added android.sourceSets.getByName("release") { jniLibs.srcDir("$buildDir/rustJniLibs/android") } and the release version of the app worked fine.

Ok, so it's a source set issue?

I commented the sourceSet lines out, confirmed that the test failed, then added android.sourceSets.getByName("main") { jniLibs.srcDir("$buildDir/rustJniLibs/android") }, and the test passed. Checked the readme for this plugin and confirmed that it doesn't say you need to add the build directory to the sourcesets.

... And then I decided to do some confirmation by doing a clean and test with no build-cache, and got an error about having duplicate libraries... (task mergeDebugJniLibFolders failed, duplicate resources, with both paths listed seeming to point to the same file.) I removed the sourceset addition line (so no manual adding to the source set and it worked again.

So, ok, we've got some serious build cache interference here and most of what's above is suspect.

In the end I commented out everything except the cargo configuration block and the "depends on CargoBuild" block". So just this:

cargo {
...
}
tasks.withType<com.android.build.gradle.tasks.MergeSourceSetFolders>().configureEach {
    if (this.name.contains("Jni")) {
        this.dependsOn(tasks.named("cargoBuild"))
    }
}

I ran that with gradle clean connectedDebugAndroidTest --no-build-cache and it worked, thought I succeeded.

But after some more testing it failed...

So I'm still not sure, but my best guess is that, when starting with a fully clean cache, the first build will fail and it'll continue to fail until you make a change to your build.gradle file which causes one of the tasks to run again. I think there's some kind of build dependency issue, maybe something changed related to when source sets are added. But I need to get going so that's where this stands at the moment...

Key takeaway: all tests here should be done without cache, after invalidating Android Studio's cache.

@stan-irl
Copy link

im seeing something kinda similar.

Im trying to run gradle on a docker container and if i run gradle build before gradle publish then I get the duplicate resource error. this doesnt happen on my local machine.

If I only run gradle publish then I dont get the error but the published aar doesnt contain any native libs. I checked the build artifacts and I can see that rustJniLibs is populated.

I dont use AGP at all though

@stan-irl
Copy link

stan-irl commented Mar 14, 2023

oh wait im on 7.3.1. i have an issue with my tooling which is updating me to 7.4 i think

@stan-irl
Copy link

Theres an issue tracking it here. looks like an AGP bug?

@stan-irl
Copy link

I've solved my issue - it was actually the issue discussed here. Interesting that it can trigger the duplicate resource error.

@SupernaviX
Copy link

SupernaviX commented Mar 29, 2023

I was running into (the same?) issues with my project after upgrading to gradle 8.0.2, where changes to my rust files weren't getting picked up in the android app. I hacked together a fix by registering the cargoBuild output folders as inputs to the mergeDebugJniLibFolders and mergeReleaseJniLibFolders tasks.

project.afterEvaluate {
    // collect all the target dirs we compile to
    def jniTargetDirs = []
    tasks.withType(com.nishtahir.CargoBuildTask).forEach {
        jniTargetDirs += new File("$buildDir/rustJniLibs", it.toolchain.folder)
    }

    // make sure we rerun the "merge jni lib folders" tasks any time they change
    tasks.matching { it.name.matches(/merge.*JniLibFolders/) }.forEach {
        jniTargetDirs.forEach { dir -> it.inputs.dir(dir) }
    }
}

@ghost
Copy link

ghost commented Mar 29, 2023

So a few weeks back I looked at the code and it's not in a great shape. IIRC pretty much everything is created afterEvaluation which makes it extra tricky to get things right.

The Android Gradle Plugin has stabilized a set of APIs that are meant for properly hooking in things like this (https://developer.android.com/studio/build/extend-agp). In the next few weeks I'm planning on basically re-implementing the Rust plugin in my project using those APIs (if anything so that we can upgrade past AGP 7.3) and I'll be able to share that code, which could probably be ported back into this plugin.

Also, it's worth noting that the tests for the plugin only check to make sure that the rust library is built, not whether it gets packaged into an apk or aar, which is why the extra tests that I tried to add weren't catching it.

@chenzhenjia
Copy link

I use this configuration to fix the problem that run will not package rustJniLibs

tasks.whenObjectAdded {
   if ((this.name == "mergeDebugJniLibFolders" || this.name == "mergeReleaseJniLibFolders")) {
        this.dependsOn("cargoBuild")
       // fix mergeDebugJniLibFolders  UP-TO-DATE
        this.inputs.dir(buildDir.resolve("rustJniLibs/android"))
    }
}

MarijnS95 added a commit to MarijnS95/AndroidNativeSurface that referenced this issue Oct 24, 2023
mozilla/rust-android-gradle#118 (comment)

Observations:
- The relevant task is called `cargoBuildArm64`, but it's ran as an
  intermediate dependency of `cargoBuild`;
- `buildDir.resolve()` doesn't exist (deprecated? It's not in the Java
  21 docs), but the sub-path `new File()` constructor does;
- `it.toolchain` on `com.nishtahir.CargoBuildTask` is `null` outside of
  `project.afterEvaluate`;
- Using purely typed `withType` queries allows one to write the
  expression without hardcoded task names, and automatically link
  all relevant tasks together.
@MarijnS95
Copy link

MarijnS95 commented Oct 24, 2023

I couldn't get the above to work 1 for various obscure reasons (timing between various callbacks, things being null, io::File::resolve() no longer existing in at least Java 21), but piecing together what was written in here and #85 lead me to either of these that seem to work consistently:

tasks.matching { it.name.matches(/merge.*JniLibFolders/) }.configureEach {
    it.inputs.dir(new File(buildDir, "rustJniLibs/android"))
    it.dependsOn("cargoBuild")
}
project.afterEvaluate {
    tasks.withType(com.nishtahir.CargoBuildTask).forEach { buildTask ->
        tasks.withType(com.android.build.gradle.tasks.MergeSourceSetFolders).configureEach {
            it.inputs.dir(new File(new File(buildDir, "rustJniLibs"), buildTask.toolchain.folder))
            it.dependsOn(buildTask)
        }
    }
}

Thanks all for posting your snippets! It's sad to see that this project has been abandoned, would be great to spend some time to get a fix for this merged instead.

Footnotes

  1. By "working" I am referring to the APK containing the latest up-to-date libxxx.so built from Rust code. The above comments fix any errors that were occurring but didn't update the final library in the APK unless a clean build is performed every time.

MarijnS95 added a commit to MarijnS95/AndroidNativeSurface that referenced this issue Oct 24, 2023
mozilla/rust-android-gradle#118 (comment)

Observations:
- The relevant task is called `cargoBuildArm64`, but it's ran as an
  intermediate dependency of `cargoBuild`;
- `buildDir.resolve()` doesn't exist (deprecated? It's not in the Java
  21 docs), but the sub-path `new File()` constructor does;
- `it.toolchain` on `com.nishtahir.CargoBuildTask` is `null` outside of
  `project.afterEvaluate`;
- Using purely typed `withType` queries allows one to write the
  expression without hardcoded task names, and automatically link
  all relevant tasks together.
@klcantrell
Copy link

Thanks @MarijnS95, I kept running into java.lang.UnsatisfiedLinkError: Error looking up function errors trying to rerun my app after modifying the Rust source. The following snippet from #118 (comment) is what fixed it for me.

tasks.matching { it.name.matches(/merge.*JniLibFolders/) }.configureEach {
    it.inputs.dir(new File(buildDir, "rustJniLibs/android"))
    it.dependsOn("cargoBuild")
}

klcantrell added a commit to klcantrell/rust-multiplatform-experiments that referenced this issue Oct 25, 2023
iOS script needed to rebuild the dynamic library since we're using the macro based binding generation

Android Gradle setup needed some workarounds to ensure the native libs were packaged properly after being rebuilt.
See mozilla/rust-android-gradle#118 (comment).

Also, the Android Gradle setup needed the `bindGen` task to depend on
`cargoBuild` because we're using macro based binding generation.
@ianthetechie
Copy link

Adding my 2 bytes of information in case it helps others.

I am currently on AGP 8.1.2 and frequently hit the same issue as @klcantrell with UnsatisfiedLinkErrors. Strangely, if I simply ran the Android Tests target (or similar), after gradle clean, then all worked fine. But if I ran gradle build first, I hit the issues. This, in my case, had nothing to do with underlying rust source changes btw; it occurred simply as a result of certain gradle orderings.

Using the same fix mentioned in that comment seem to solve the issues for my case.

@ngoquang2708
Copy link

My alternative is to use cmake with corrossion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants