Problem
I am developing an Android library. This library lives in a multi-module Gradle project with the following structure:
├── legacy
│ ├── src
│ │ └──...
│ └── build.gradle
├── data
│ ├── src
│ │ └──...
│ └── build.gradle
├── feature
│ ├── src
│ │ └──...
│ └── build.gradle
└── settings.gradle
data
depends on legacy
.
// data/build.gradle
implementation project(":legacy")
feature
depends on data
and legacy
.
// feature/build.gradle
implementation project(":legacy")
implementation project(":data")
feature
, data
, and legacy
are all Android library modules. feature
is the published Android library. data
and legacy
are not used outside this project, and are therefore not published anywhere.
My Android application depends on the published feature
AAR artifact.
implementation "feature:<version>" // package omitted for obfuscation
Gradle has no problem finding the feature
library. However, Gradle throws an error because it wants to find data
and legacy
.
Execution failed for task ':mobile-app:mergeDevDebugNativeLibs'.
> Could not resolve all files for configuration ':mobile-app:devDebugRuntimeClasspath'.
> Could not find feature:data:unspecified.
Searched in the following locations:
- ... // omitted
Required by:
project :mobile-app > feature:<version>
I don't think it matters, but the mobile-app
project is using a locking plugin, Nebula's Gradle Dependency Lock Plugin.
Attempted fixes
I'm a little confused why this error is happening at all. I expected the classes of legacy
and data
to be included in the feature
AAR. That is clearly not the case. If that can be done, I prefer that solution.
Here are some dependency variations I've tried so far.
// feature/build.gradle
// doesn't work; same error
api project(":legacy")
api project(":data")
// doesn't work; mobile-app doesn't complain about missing artifacts, but fails to compile:
// cannot find symbol class <class in legacy>
compileOnly project(":legacy")
compileOnly project(":data")
I can successfully compile and run mobile-app
if I use dependency substitution to replace the feature
AAR with my local copy of the code repo.
// settings.gradle of mobile-app project
includeBuild('../feature') { // this is the local relative path to the project
dependencySubstitution {
substitute module('feature') using project(':feature')
}
}
However, this is not an appropriate solution to publish and subsequently enforce upon the entire dev team.
CodePudding user response:
Gradle modules map 1:1, more or less, with Java/Android libraries.
Adding a dependency (implementation
or api
) to a library does not cause that dependency to get compiled into the library. Otherwise you would get class path collisions if you depended on two libraries which themselves depended on the same library as each other.
Instead, adding a dependency to a library adds that dependency to the library's POM and/or gradle.module file, which gets published along with the JAR or AAR to the repository. Consumers of the library (i.e. other Gradle or Maven projects, like your app) then parse that POM, and thus know which additional dependencies to download.
The way I see it, your options are:
- Publish
data
andlegacy
to a repository your app can consume, just likefeature
. Your app won't consume them directly, but they'll be there for Gradle to download when it sees thatfeature
requires them. - Move the code from
data
andlegacy
inside offeature
(and mark it all as Kotlininternal
or Java package-private to prevent it from being public API). - Refactor
feature
to publicly define all of its data sources as interfaces. Move thedata
andlegacy
modules into your app project and refactor them to provide the necessary concrete implementations offeature
's required interfaces.