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.
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 and legacy to a repository your app can consume, just like feature. Your app won't consume them directly, but they'll be there for Gradle to download when it sees that feature requires them.
Move the code from data and legacy inside of feature (and mark it all as Kotlin internal or Java package-private to prevent it from being public API).
Refactor feature to publicly define all of its data sources as interfaces. Move the data and legacy modules into your app project and refactor them to provide the necessary concrete implementations of feature's required interfaces.
Related
Our company requires we have all our dependencies downloaded from our internal repository (Artifactory). Our Android project has the main Android modules, plus composite builds.
I was able to replicate the issue creating a simple project from Android Studio, and adding a composite build (or buildSrc) into it.
More or less like this (I didn't include the other files above to keep it simple):
project
├─ app (<-- application module)
├─ plugins (<-- composite build )
│ ├─ settings.gradle.kts (<-- settings for composite build)
├─ settings.gradle.kts (<-- settings for main project)
Both settings.gradle.kts (main + composite build projects) have something like this (again, leaving irrelevant elements out of it):
pluginManagement {
repositories{
maven(url="https://artifactory.mycompany.org")
}
}
dependencyResolutionManagement {
repositories{
maven(url="https://artifactory.mycompany.org")
}
}
This works perfectly while running from terminal (e.g. ./gradlew build), but the sync operation via Android Studio seems to always trigger dependencies download from *.gradle.org. After the sync, all the other project dependencies for build operations seem to work on Android Studio. If I create a project without composite builds (or buildSrc) it seems the issue doesn't happen.
This has been creating many issues, as you can imagine, but it doesn't seem that many people have been reporting the exact same experience. Apart from the issue of downloading dependencies from a different repository, it is also downloading these dependencies multiple times during the day.
I've also checked environment variables, Java SDK, etc to match between Android Studio and terminal, and Android Studio is also configured to use gradle from "gradle-wrapper.properties".
Has anyone faced similar issue, and has any clues on what else I could be missing? Or could that be an issue on the Android Studio gradle plugin?
I'm looking to update Gradle from v4 to v5 but I'm getting the below warning on v4:
Gradle now uses separate output directories for each JVM language, but
this build assumes a single directory for all classes from a source
set. This behaviour has been deprecated and is scheduled to be removed
in Gradle 5.0.
So I believe I need to get rid of this warning before I can update to v5. Problem is I'm not really sure what it's asking me to do. How do I cahnge the build so that it doesn't assume a single directroy for all classes from a source set?
I tried adding the below to the build.gradle but I'm still getting the warning:
sourceSets {
main {
// Compiled Java classes should use this directory
java.outputDir = new File(buildDir, "classes/java/main")
}
}
You don't need to add anything to the build script.You just need to organize your project like this:
├── build.gradle
├── settings.gradle
└── src
└── main
├── java
│ └── HelloWorld.java
└── kotlin
└── Utils.kt
That deprecation message is shown when you use sourceSet.output.classesDir, which returns a File.
This has been replaced in Gradle 4.x, and removed in Gradle 5.x, by sourceSet.output.classesDirs (note the s at the end) which returns a FileCollection.
So you need to figure out where you use that in your buildscript and if not which plugin does. Note that the new method is already available in Gradle 4.x and so you should be able to upgrade your code or plugin version to make the deprecation message go away.
Good hunting!
I've got this shared common classes that I extracted into a separate "common" module with gradle (which has dependencies on some jars). I defined it as a java project because I want to also use it outside of the android context. But when I include it in my android project with:
compile project(':my_common')
It doesn't seem to work.
I have two following Android Studio project, structure like this:
projectA/
├----build.gradle
├----settings.gradle
├----bluewhale/
├----krill/
projectA settings.gradle file:include 'bluewhale', 'krill'
projectB/
├----build.gradle
├----settings.gradle
├----hello/
├----krill/
projectB settings.gradle file:include 'hello', 'krill'
You can see "projectA" and "projectB" contain the same module "krill". Actually, it's a library project.
My question is: how to reuse the submodule "krill" in Gradle? I don't want to include the same copy of "krill" in every project
Looking forward to your reply! Thanks!
If you have a sub-module that is used in multiple projects, you should think about extracting it to a separate project. Then you can create a dependency out of it and include it in both projects in dependencies section.
If you only use your local machine for development, without any custom repository, the best way would probably be to use the mavenLocal() repository. You can use the maven publish plugin to publish your jar into the local maven repository. It should be as simple as adding this to the new krill:
apply plugin: 'maven'
apply plugin: 'maven-publish'
publishing {
publications {
maven(MavenPublication) {
from components.java
artifact sourceJar {
classifier "sources"
}
}
}
}
repositories {
mavenLocal()
}
You might want to set the group and artifact ID. See the documentation for more info.
You can also keep krill in one of the projects, let's say ProjectA, if it has some relation to it. Then you set up the maven publishing in the krill sub-module. You can also publish to maven local by running gradle :krill:publishToMavenLocal and then use it as dependency in ProjectB.
Another option is to save the submodule outside the projectA and projectB trees and add it using something like this:
include("krill")
project(":krill") {
projectDir = new File("$settingsDir/../krill")
}
But I can't recommend this because it's hacky and your IDE might have a problem with it too.
Last thing that might be possible is to create symlinks from a directory where your krill project is located to both ProjectA and ProjectB. But that is a really bad idea e.g. when you are using a version control.
Finally I found an article here: an-alternative-multiproject-setup-for-android-studio. It works for me perfectly!
It shows us another Way different from Google’s Gradle Plugin user guide recommends
Sample code below: (Add this script to your project settings.gradle file)
include ':krill'
project(':krill').projectDir = new File('../otherProject/krill')
Project structure below:
RootFolder/
├----projectA/
│ ├----build.gradle
│ ├----settings.gradle
│ └----bluewhale/
│
├----projectB/
│ ├----build.gradle
│ ├----settings.gradle
│ └----hello/
│
└----otherProject/
├----krill/
│ └----build.gradle
│
└----otherModule/
└----build.gradle
For more details, visit gradle official document: Multi Project Builds
I have an Android Gradle 1.1 Project with several Modules that have some dependency on the others.
Project
├ Module2
│ ├ src/main: imports classes from Module1
│ └ src/test: imports classes from Module1, tests Module2/src/main
└ Module1
├ src/main: implements classes needed by Module2/src/main & Module2/src/test
└ src/test: tests Module1/src/main
My Modules themselves compile fine.
My question is related to testing the modules.
(FYI: I am using Robolectric)
I have seen several "similar" questions related to Android build & test dependencies, but none of the ones that I have found seem to be asking what I am looking for:
Android Gradle 1.1 - adding a test dependency on another project's tests
How to run unit tests with dependency to an Android library module?
Gradle Android unit tests that depend on an 'aar'
Multi-project test dependencies with gradle
Jacoco and Unit Tests Code Coverage with android-gradle-plugin >= 1.1
Android Studio module that depends on another module's tests
These questions are also either old, not focused on using Android Gradle 1.1+ built in Unit Test abilities, or not really that relevant.
What I am asking is: "How can a [java compiled?] unit test import another android module (normally a .aar file) in the same project?"
A "testCompile project(':module1')" doesn't seem to do the trick.
I suspect that I want to either:
Compile the other module that I depend on as a .jar file
Use the other module's already compiled .class files
Extract the .class files from the other module's .aar file
Find some other built in way to do this
Is there a slick way to do this built in to the Android/Gradle build process?
Am I missing something obvious here?
Thanks!
Pv
The secret is to have project.properties and test-project.properties file next to your AndroidManifest.xml
At the test-project.properties insert all your exploded aar resource path (e.g. android.library.reference.3=../../build/intermediates/exploded-aar/AndroidStudioAndRobolectric/core/unspecified)
I explain this a bit more at http://nenick-android.blogspot.de/2015/02/android-studio-110-beta-4-and.html And a ready to use example can be found at https://github.com/nenick/AndroidStudioAndRobolectric/tree/library
If your module under test is depending on pure Java/non-Android code, an alternative approach is to extract out a new java-library module (one that builds a .jar rather than an .aar) and depend on that. Unit tests can run fine in this case.
(Probably not so helpful for the original poster since they are using Robolectric but still)