SharedTest got warning "Duplicate content root detected" on Android Studio Chipmunk - android

After upgrade to Android Studio Chipmunk, my test failed because I can't access file inside shared folder that defined in build.gradle like this.
sourceSets {
androidTest.java.srcDirs += "src/sharedTest/java"
test.java.srcDirs += "src/sharedTest/java" }
It show warning pop up with message "Duplicate content root detected". Path [sharedTest] of module [unitTest] was removed from modules [androidTest]. Anyone can resolve this?

According to https://issuetracker.google.com/issues/232007221 ("Duplicate content roots detected" with Android Gradle plugin 7.2.0) Google no longer supports this construct in Android Studio Chipmunk 2021.2.1.
https://issuetracker.google.com/issues/232007221#comment17 says "Source sets can no longer contain shared roots as this is impossible to represent in the IDE."
To follow the on-going discussions, subscribe to
https://issuetracker.google.com/232007221 and
https://issuetracker.google.com/232420188

https://issuetracker.google.com/issues/232420188#comment19
The current recommendation is to use a separate com.android.library Gradle project to store any shared code required across test and androidTest.

According to (#kreker thx for the hint): https://issuetracker.google.com/issues/232420188#comment19
The current recommendation is to use a separate com.android.library Gradle project to store any shared code required across test and androidTest.
But often (at least for me) it is enough just to create a separate java project, move the shared test code into this new project and create two additional testImplementation and androidTestImplementation project dependencies to the new shared project.
Step-by-step (maybe it helps) I did it as follows: 1. next to the app folder create a new folder called sharedTest (or something similar). 2. create the sub-directories sharedTest/src/main. 3. Move (or rather git mv in order not to loose the version history) the shared test code: git mv app/src/sharedTest/java sharedTest/src/main/ (and do not forget to check-in). 3. in sharedTest create a new (minimal) sharedTest/build.gradle.kts file:
plugins {
java
}
dependencies {
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
4.Edit the settings.gradle.kts file and add the new shared project: include(":sharedTest"). 5. Edit the app/build.gradle.kts file: remove the conflicting shared source-set section android{...} and add 2 new dependencies:
dependencies {
//Share Code between androidTest and test
//https://stackoverflow.com/questions/72358843/sharedtest-got-warning-duplicate-content-root-detected-on-android-studio-chipm
testImplementation(project(path = ":sharedTest"))
androidTestImplementation(project(path = ":sharedTest"))
}

Related

Gradle/Android: includeBuild sets build variant to null

Had no luck posting through the gradle community, so I thought I would reach out here.
In my root settings.gradle file, I use includeBuild to include the projects of interest. This works and I see that the builds have been included. Each one of these android libraries may contain a dependency on another module in a different project in the monorepo. All of these libraries produce artifacts that are published, so normally I would just target the recently released artifact. I want to use includeBuild with dependency substitution when developing locally. This way, if I make a change in a dependency I can make changes in all dependents immediately without having to release an artifact first.
Let me show you an example of one that I have working:
I have directory called base-implementation, this directory contains two gradle projects: base-api and base-ui-api. Each of these projects contains one singular android library module api and ui-api respectively. To further illustrate, one drilldown from a package structure would go base-implementation → base-api → api where api may be defined as an artifact dependency in other android libraries in this monorepo. For example ui-api defines a dependency on api.
Given this structure, in my base-ui-api project, I define an includeBuild on base-api and substitute out the dependency with the android library api. That looks like this:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
The isIntegrationBuild is just a gradle property I have set up as a development flag to use dependency substitution. I can run the gradle task provided by android called androidDependencies which will list all resolved dependencies for a given build. I can verify here that the dependency is indeed subbed out in ui-api by confirming this line “:api (variant: debug)”
It is important to note at this point, every single android library in this mono repo has only two build variants of debug and release there are no inconsistencies here with build variants.
The problem I am running into is this. I have a library that defines a dependency on another library which then defines a dependency on api. When going to build this library, a random nullPointerException is thrown without any message. What I have noticed however, is this; When I run androidDependencies task on the library that it is dependent on (the library that contains a dependency on api), the subbed out api dependency comes back with this “:api (variant: null)” and I cannot for the life of me figure out why. All includeBuilds and dependency substitution definitions are exactly the same. yet one resolves with variant debug and the other does not. They have next to identical build.gradle files at both the project and module level as well. There is nothing special happening to explicitly define a default variant implementation and all variants match across all libraries.
If what I explained above is unclear, let me drive home the issue with the project structure visual that doesn’t work. There is sensitive information in these library names so for sake of censoring, I will replace their names with A and B. Where A depends on B and B depends on api. A and B also share the same root dir but this root dir is just a container. Not a gradle project.
A depends on B so in the project level settings.gradle of A I define an includeBuild with a dependency substitution for B. This looks like the following:
def BDependency = "com.example.b:b"
if (isIntegrationBuild.toBoolean()) {
includeBuild('../project-b') {
dependencySubstitution {
substitute module(BDependency) using project(':b')
}
}
}
This block runs and works, I can see and confirm that the artifact is swapped out with the included build.
Now, B defines a dependency on api so its project level settings.gradle looks almost identical to the first case I stated where ui-api depends on api the only difference is relative pathing for includeBuild. This looks like the following:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
Why is that when I build ui-api it pulls in api of variant debug, but when I build B it pulls in api of variant null? Is there a something I am missing about nested includedBuilds?

How to use different settings.gradle files for different environments

The problem
I have two projects, A (ui) and B (background service). Project A has a dependency on B. Project B gets published to a maven repository and included in project A like so in build.gradle
debugImplementation ('com.example:project-B:0.0.0-SNAPSHOT') { changing = true }
releaseImplementation ('com.example:project-B:1.6.2')
This works, but it's a pain to validate my service changes on the UI side. I need to publish project B to my nexus repo and resync project A.
I changed project A to the following:
build.gradle:
debugImplementation project(":project-b")
settings.gradle:
include ':project-a'
include 'project-b'
project(':project-b').projectDir = new File(settingsDir, "${project-b-path}")
I can have all my code in one IDE window and have A use local instance of B. But the problem is this will break on my build server since there is no local B project, only the one on nexus.
Is there a way to configure the settings.gradle for release vs debug? I can just commit my changes and overwrite the file on the build server, but I want to know if there are other ways?
You can use gradle command line to set which settings or build file should be used.
Settings File
-c, --settings-file
Specifies the settings file. For example: gradle --settings-file=somewhere/else/settings.gradle
Build File
-b, --build-file
Specifies the build file. For example: gradle --build-file=foo.gradle. The default is build.gradle, then build.gradle.kts, then myProjectName.gradle.
You can find more details here: Gradle docs: Environment options

How to add a new source directory to an Android Studio project?

So ultimately I'm trying to separate my integration tests from the unit tests in an Android Studio project. I've found a few resources on the subject:
http://selimober.com/blog/2014/01/24/separate-unit-and-integration-tests-using-gradle/
https://blog.safaribooksonline.com/2013/08/22/gradle-test-organization/
Separating integration tests from unit tests in Android Studio
All these seem to indicate that the way to go is to create a new sourceSet for the integration tests, and then to create a new test task which builds and runs the tests in that source set. I can't get past the first step of creating a source set which is recognized by Android Studio.
Here's what I have within app/build.gradle, which builds without errors, but does not result in an integrationTest source root I can add classes to:
android{
...
sourceSets{
integrationTest {
java.srcDir('src/integrationTest/java')
}
}
}
My questions are:
Where precisely do I have to add the sourceSets block? In build.gradle? in app/build.gradle? In app/build.gradle inside the android block?
Once I've added my source set in he right place using the correct syntax, is this sufficient for Android Studio to detect and present it in the UI along side the main and test sources, or are there additional steps?
edit:
I've attempted to follow the instructions in marius' answer, but integrationTest isn't showing up in my build variants. Here's what I'm seeing:
This is enough:
android{
...
productFlavors{
integrationTest {
}
}
}
Regarding your 1st question: The productFlavors block should be in your app/build.gradle, inside android block.
Regarding your 2nd question: Once you add this to your build.gradle file, you also need to create your folders /src/integrationTest and /src/integrationTest/java . Once that is done, sync your gradle files and choose your new Build Variant from the Build Variant window, in order for the IDE to detect it as the active source folder.

Exclude a class from the build in Android Studio

I've been learning Android over the past few months and have been using Eclipse v4.2 (Juno) as my IDE.
I am trying to migrate to Android Studio. How can I exclude some of the classes from build path I have yet to complete?
In Eclipse, it was a straightforward right click. I can't find any reference to it in Android Studio.
AFAIK, IntelliJ allows to exclude packages. Open Project Structure (Ctrl + Alt + Shift + S in Linux) → Modules → Sources tab.
However, if you would like to exclude only one class, use the Gradle build file.
Android Studio uses Gradle, so in the build.gradle file, add a custom SourceSet inside the android configuration that excludes your class, e.g.:
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
defaultConfig {
minSdkVersion 19
targetSdkVersion 19
packageName "org.homelab.lab"
testPackageName "org.homelab.lab.test"
}
sourceSets {
main {
java {
exclude '**/SomeExcludedClass.java'
}
}
androidTest {
java {
exclude '**/TestSomeExcludedClass.java'
}
}
}
}
It works fine with Android Studio v3.0:
apply plugin: 'com.android.application'
android {
defaultConfig {...}
buildTypes {...}
sourceSets {
main {
java {
exclude 'com/example/full/package/path/MainActivity.java'
}
}
}
}
It can't be done.
Maybe it could back in May 2013 when the accepted answer was provided, but not anymore (as of Android Studio 1.2).
Here is the issue: Sourceset component should be more like the Java one
According to the labels they are targetting Android Studio 1.5 for adding these feature.
Cross posting from https://stackoverflow.com/a/69261642/3689782 but it seems useful to repeat here.
I came across a way to make this work specifically for Android unit tests (but I'm assuming it's adaptable) using a combination of other solutions from the link above:
def filesToExclude = [
'**/*TestOne*.kt',
'**/*TestTwo*.kt',
...
]
tasks.withType(org.gradle.api.tasks.SourceTask.class).configureEach {
it.exclude(filesToExclude)
}
android.sourceSets.test.kotlin.exclude(filesToExclude)
In my particular case, the extra wildcards around the test name were needed due to other generation occurring (specifically, Dagger with kapt).
This seems to be a bit hacky way to approach it, but it works by ensuring the test target is excluded from all tasks that it could actually be excluded from (including both build & kapt tasks). The sourceSets exclusion is still necessary for the file not to be picked up for compilation (I think this is the Kotlin Gradle Plugin doing it, but it might also be Android Gradle Plugin--I'm not well versed enough in debugging Gradle builds to pin it down).
The way I used to do the same was by,
For Windows: Right click on the Java file → Show in Explorer → change extension of the file from '.java' to '.c'.
For Mac: Right click on the Java file → Reveal in Finder → change the extension of the file from '.java' to '.c'
It is as simple as that.
For my case I need to prevent a whole folder then I did it by this -
sourceSets {
main {
jniLibs.srcDirs = ['libs']
java {
exclude 'com/example/myfolder'
/* The holder name I want to excludes its all classes */
}
}
}
Move it to a new folder.
Right-click → Show in explorer → cut and then paste to a new folder (outside of any project).
I just created a new folder inside of AndroidStudioProjects folder and placed them there.

Gradle Configurations not Working as Expected in New Android Build System

Environment Configuration
com.android.tools.build:gradle:0.4
gradle version 1.6
jdk 1.6 (OSX)
android build tools version 17
compile sdk version 17
The issue that I seem to be having is that I can’t seem to exclude lombok from being added to the apk. I tried to do it by creating a provided configuration like this:
configurations {
provided
}
sourceSets {
main { compileClasspath += configurations.provided }
}
and then adding the dependency like this:
dependencies {
provided ‘org.projectlombok:lombok:0.11.8′
}
But I’m still getting this error:
Error: duplicate files during packaging of APK <myapp>.apk
Path in archive: LICENSE
Origin 1: /<home>/.gradle/caches/artifacts-24/filestore/org.projectlombok/lombok/0.11.8/jar/e43ce2be16d8990568a4182c0bf996ad3ff0ba42/lombok-0.11.8.jar
Origin 2: /<home>/.gradle/caches/artifacts-24/filestore/org.sonatype.sisu.inject/cglib/2.2.1-v20090111/jar/7ce5e983fd0e6c78346f4c9cbfa39d83049dda2/cglib-2.2.1-v20090111.jar
:packageRelease FAILED
I have tried using lombok-api.jar which then causes a different issue regarding some AccessLevel annotation while performing dex.
Which suggests that its including the lombok jar file into the apk. This shouldn't be happening, any suggestions?
You can't use sourceSets because we use custom ones. You'd have to do the following:
android.applicationVariants.each { variant ->
variant.javaCompile.classpath += configurations.provided.
}
However, it should be possible to instead remove the dependency from our "package" config (which replaces the "runtime" one.) I'll look into it.

Categories

Resources