autonomousapps / autonomousapps/dependency-analysis-gradle-plugin
KMP: invisible inline-member usage in `commonMain`
- Dominant language
- Kotlin
- Stars
- 2.2k
- Forks
- 158
- Avg merge
- 16h 25m
- Merged PRs (30d)
- 46
Description
**Plugin version**
3.17.0
**Gradle version**
9.6.1
**JDK version**
26
**Kotlin and Kotlin Gradle Plugin (KGP) version**
2.4.10
**Android Gradle Plugin (AGP) version**
9.3.0
**`reason` output for bugs relating to incorrect advice**
```
------------------------------------------------------------
You asked about the dependency 'com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1 (libs.kotlin.result.coroutines)'.
You have been advised to remove this dependency from 'commonMainImplementation'.
------------------------------------------------------------
Shortest path from :core:database to com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1 (libs.kotlin.result.coroutines) for androidCompileClasspath:
:core:database
\--- com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1
Source: androidMain
-------------------
(no usages)
```
Following the advice fails compilation:
```
e: file://.../core/database/src/commonMain/kotlin/.../DefaultLocalDataSource.kt:9:38 Unresolved reference 'coroutines'.
e: file://.../core/database/src/commonMain/kotlin/.../DefaultLocalDataSource.kt:51:16 Unresolved reference 'runSuspendCatching'.
```
**Describe the bug**
In a KMP module, a `commonMain` dependency whose only used API is an `inline` function is reported as unused, even though removing it breaks compilation.
The module has in `commonMain`:
```kotlin
import com.github.michaelbull.result.coroutines.runSuspendCatching
```
`runSuspendCatching` is `inline`, which it seems like DAGP handles with import matching (`FindKotlinMagicTask`, `ComputeUsagesTask.usesInlineMember`), which compares the project's source imports against inline members found in dependency jars.
I did some digging and the producer half works. `intermediates/inline-usage.json` for the `androidMain` analysis correctly lists the artifact's inline members:
```json
{
"coordinates": { "identifier": "com.michael-bull.kotlin-result:kotlin-result-coroutines-jvm", ... },
"inlineMembers": [
{ "className": "com.github.michaelbull.result.coroutines.RunSuspendCatchingKt",
"packageName": "com.github.michaelbull.result.coroutines",
"inlineMembers": ["runSuspendCatching"] }, ...
]
}
```
The consumer half never sees the import. `intermediates/exploded-source.json` for the same analysis contains only the `androidMain` file:
```json
[ { "relativePath": "src/androidMain/kotlin/.../DatabaseTypeFactory.android.kt", ... } ]
```
The `commonMain` file containing the `runSuspendCatching` import is absent, so `usesInlineMember` finds no matching import and the dependency falls through to `Bucket.NONE`.
The cause appears to be in source collection for KMP compilations. `KmpSourceSet` uses only the compilation's default source set:
```kotlin
// internal/analyzer/JvmSourceSet.kt
override val sourceCode: SourceDirectorySet = compilation.defaultSourceSet.kotlin
```
For the Android compilation of a KMP module, `defaultSourceSet` is `androidMain`; the `dependsOn` closure (`commonMain`, `nativeMain`, etc.) is not included. So any usage evidence that exists only in common source (e.g. inline function imports) is invisible to every per-compilation analysis. (`KotlinCommonSources.kt` collects `commonMain`/`commonTest` sources, but its only caller is the unused `ListSourceFilesTask`.)
**To Reproduce**
1. Create a KMP library module with an Android target.
2. In `commonMain`, declare `implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.1")`.
3. In `commonMain` source, call `runSuspendCatching { ... }` (its result must flow into non-inline API from the base `kotlin-result` artifact, which is typical).
4. Run `buildHealth`: the dependency is advised for removal from `commonMainImplementation`.
5. Remove it: compilation fails with `Unresolved reference 'runSuspendCatching'`.
**Expected behavior**
The usage of the inline function is picked up.
**Additional context**
Workaround:
```kotlin
dependencyAnalysis {
issues {
all {
onUnusedDependencies {
exclude("com.michael-bull.kotlin-result:kotlin-result-coroutines")
}
}
}
}
```
Contributor guide
Research direction
Start with internal/analyzer/JvmSourceSet.kt, especially KmpSourceSet's sourceCode, then read KotlinCommonSources.kt and its callers. Reproduce the KMP example and run buildHealth to trace why commonMain imports are absent from exploded-source.json. Done means the inline usage is detected and the dependency is no longer incorrectly reported as unused.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100