How to put local dependencies first when calling gradle idea? - java

When calling gradle idea, external dependencies are ordered first in the class path relatively to local Jar inclusions. As such :
dependencies {
compile fileTree(dir: 'libs', include:['*.jar'])
compile group: 'foo', name:'bar', version:'1.0.0'
}
will include my local jars last. This is a problem in my project as these jars' purpose is to partially overwrite the external library.
The same behavior is observed when specifying the repository as a source of dependencies using flatDir and loading the jar without fileTree. It is put last in the classpath.
I have found several mentions of the problem when researching, such as https://discuss.gradle.org/t/gradle-messes-up-the-classpath-order-in-generated-projects-when-there-are-mixed-dependency-types/13130, but no workarounds.
I suppose these exist, gradle being very customisable, but being very new to it my attempts to make one fail. How to proceed?

I'm not using IntelliJ on a regular basis but tried it in the context of this question and my impression is that gradle's idea plugin and IntelliJ's gradle plugin don't go well together. That is you should either use the idea gradle plugin and import as plain Java project or import as gradle project using IntelliJ's gradle plugin. Main reason is that the idea plugin and the IntelliJ plugin are generating slightly different iml-files (those files are holding the project dependencies - amongst others) which leads to lot of confusion when using both plugins together. As you specifically asked for the gradle idea plugin, I used this plugin and imported into IntelliJ as plain java project.
But to answer your question I found no evidence that the order of libraries on the classpath differs from the order as declared in the dependencies section of the gradle file, when using a flatDir repo. When using compile fileTree(dir: 'libs', include:['*.jar']) the order was actually broken as described in your question. That is, you should stick to using a flatDir repo.
I'm using gradle 4.9 and IntelliJ 2018.2.
This is my gradle file
apply plugin: 'java'
apply plugin: 'idea'
repositories {
jcenter()
flatDir {
dirs 'libs'
}
}
dependencies {
compile 'zzz:zzz-0.0.0'
compile 'aaa:aaa-0.0.0'
compile 'com.google.guava:guava:24.0-jre'
compile group: 'javax.websocket', name: 'javax.websocket-api', version: '1.1'
}
task wrapper(type: Wrapper) {
gradleVersion = '4.9'
distributionUrl = "http://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip"
}
In my libs folder there are two jars aaa-0.0.0.jar and zzz-0.0.0.jar both are copies of guava-24.0-jre.jar. That is all guava classes are present in both jars as well. As zzz:zzz-0.0.0 is the first dependency in the gradle file, the expectation would be that guava classes are being loaded from zzz-0.0.0.jar instead of guava-24.0-jre.jar or aaa-0.0.0.jar. I used the following main class to test this:
package test;
import com.google.common.math.LongMath;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(LongMath.class.getProtectionDomain().getCodeSource().getLocation().toURI());
}
}
And the output when running it from IntelliJ is
file:/C:/ws/gradle-idea-test/libs/zzz-0.0.0.jar
That is the com.google.common.math.LongMath class is indeed being loaded from the local libs/zzz-0.0.0.jar instead of the guava-24.0-jre.jar.
I noticed that the list of external dependencies in IntelliJ doesn't show the local libraries. And even more confusing the libraries are ordered alphabetically and don't reflect the actual order on the classpath which might be quite confusing:
To get the actual order of elements on the classpath you will have to look in the module dependencies section in the module settings ("Open Module Settings" > "Project" > "Modules" > "Dependencies Tab") which looks like this:
As you can see the dependencies are listed in correct order and include the local libraries as well. The order of libs in this dialog is basically the same as in the generated iml-file.
When using the IntelliJ gradle plugin instead of gradle's idea plugin, IntelliJ basically behaved the same way but the generated iml-file looked different and the external libraries were displayed in a different format. But there was no difference regarding the classpath order.

Related

Gradle dependencies: package does not exist

I know this has been asked multiple times but the questions have multiple answers.
I'm trying to use a Java package that's a dependency of my dependency. Let's say I've built this gradle project called "ee_tools". And that has a dependency called "my_models", which has a package called "com.mycompany.my_models.db". So in the ee_tools project build.gradle, I've got
dependencies {
// My stuff
implementation group: "com.mycompany", name: "my_models", version: "1.0.0"
}
Then in my current project, I've got
dependencies {
// My stuff
implementation group: "com.mycompany", name: "ee_tools", version: "1.0.0"
Shouldn't this mean that the public classes in my_models are accessible through ee_tools to my current project? Gradle was able to find both in my artifactory instance. And the gradle dependencies command shows ee_tools under the compileClasspath, implementation, and testCompileClasspath trees, but not with its children dependencies. It also shows up in the runtimeClasspath and testRuntimeClasspath trees with its children dependencies, including my_models.
I am also able to see that package inside the jar on the left side of IntelliJ, under the "External Libraries" tree, along with some classes.
But when I try to use the my_models package in my current project, IntelliJ can't find it and it fails a gradle build with the error
error: package com.company.my_models.db does not exist
It can't find any package in that dependency. What gives? Am I declaring the dependencies incorrectly? Or is this a gap between the gradle binary in my command line vs IntellJ and gradlew?
If ee_tools depends on my_models, then your gradle file in ee_tools should be like
implementation project(path: ":path:to:my_models", configuration: 'default')
:path:to:my_models is defined in settings.gradle in project root path like this:
include ':path:to:my_models'

IntelliJ IDEA Gradle project not recognizing/locating Antlr generated sources

I'm using Antlr in a simple Kotlin/Gradle project, and while my Gradle build is generating Antlr sources, they are not available for importing into the project.
As you can see (on the left), the classes (Lexer/Parser, etc.) are being generated. I have also configured this generated-src/antlr/main directory as a Source Root. Most questions I see list this as a solution, but I've already done it.
The issue persists after multiple rebuilds (both in IDEA and on the CLI), and following all the usual "Invalidate Cache and Restart" issues.
Further, the import issue is listed in the Gradle build on the CLI so it doesn't seem isolated to IDEA.
What am I missing here?
Here's the build.gradle file produced by IDEA when I was creating the project initially, and which IDEA is using for project/workspace synchronization.
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}
group 'com.craigotis'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
apply plugin: 'antlr'
dependencies {
antlr "org.antlr:antlr4:4.5"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Try adding this to your build.gradle:
sourceSets {
main.java.srcDirs += "${project.buildDir}/generated-src/antlr/main"
}
generateGrammarSource {
arguments += ["-visitor", "-package", "com.craigotis.sprint.core.antlr"]
outputDirectory = file("${project.buildDir}/generated-src/antlr/main/com/craigotis/sprint/core/antlr")
}
compileKotlin.dependsOn generateGrammarSource
Shouldn't it locate the compiled classes and not the sources? Do you see the antlr generated classes in the target directory?
Try this: first build the project without referencing or using any ANTLR generated classes, and only after the build is successful, then add the code that references them.
(In other words, what I think that happens, is that your ANTLR sources are compiled after the code that references them. They never have a chance to compile because build fails before)
Also if this is really the case, you can solve it also by splitting into two artifacts and make sure the ANTLR one is built before the one with the code that uses it
Try to add generated sources in idea module like this post from Daniel Dekany here:
apply plugin: "idea"
...
sourceSets.main.java.srcDir new File(buildDir, 'generated/javacc')
idea {
module {
// Marks the already(!) added srcDir as "generated"
generatedSourceDirs += file('build/generated/javacc')
}
}

Switching dependencies in Gradle depending on platform

I am trying to get Gradle to select different dependencies in my multiproject build based on whether I am building for desktop or for Android. I have a common subproject (a library) I am trying to reuse. However, I cannot get Gradle to correctly switch dependency configurations.
My main settings.gradle simply includes all the dependencies:
// /settings.gradle
rootProject.name = 'myProject'
include 'androidUI'
include 'reusableLibrary'
include 'desktopUI'
Now both androidUI and desktopUI specify reusableLibrary as a dependency:
// /androidUI/build.gradle and /desktopUI/build.gradle
apply plugin: 'java'
dependencies {
compile project(path: ':reusableLibrary', configuration: 'desktop')
}
reusableLibrary itself specifies two configurations, because its dependencies are different whether it's building on desktop or Android:
// /reusableLibrary/build.gradle
apply plugin: 'java'
configurations {
desktop {
extendsFrom compile
}
android {
extendsFrom compile
}
}
dependencies {
// Just examples, the real list is longer.
// The point is that h2database is only included on desktop,
// and ormlite is only included on Android.
android 'com.j256.ormlite:ormlite-jdbc:5.0'
desktop 'com.h2database:h2:1.4.192'
}
This looks fine to me. But when I compile either desktopUI or androidUI, I can see that although the dependencies of reusableLibrary are being included on the classpath in the manner I desire, the actual JAR provided by reusableLibrary itself is not included. This of course causes the build to fail. I suspect I'm not setting up reusableLibrary correctly; I'm not clear on what the configurations {} blocks do.
Why aren't the compiled items in reusableLibrary being included on the classpaths of the UI projects? And what is the canonical way to include platform-specific dependencies in this manner?
The original configuration is pretty close to right. The key is to understand this dependency graph from the Gradle Java plugin's documentation:
This is a visualization of the Java plugin's various dependency configurations, which is Gradle-ese for "list of dependencies." When you add compile lines to a dependencies {...} block, you're adding Dependency elements to the compile dependency list.
The default dependency configuration is special; it is the one included by a compile project("path") line unless a different one is chosen with the configuration: argument. This means that when you build the library, the runtime dependency list (which includes the compiled jar from the library itself) is added to the classpath of the client project.
The original configuration creates two new nodes, desktop and android in this graph, and couples them both to compile by using extendsFrom. They are not otherwise connected to the graph! Now the problem with the original configuration is apparent: by switching the upstream project to either of these, it is missing the compiled code from runtime. This explains the classpath omission.
The solution is a bit more subtle than just aiming desktop and android at runtime. In order to ensure that everything is correctly decoupled when we add tests, we need one extra layer of dependency configurations to keep testCompile from indirectly depending on runtime. Additionally, the library's source code itself may need things on its classpath just to typecheck; we can use compileOnly for this. The end solution looks like this:
configurations {
desktopCompile
androidCompile
compileOnly.extendsFrom desktopCompile
testCompile.extendsFrom desktopCompile // Assuming tests run on the desktop
desktop {
extendsFrom desktopCompile
extendsFrom runtime
}
android {
extendsFrom androidCompile
extendsFrom runtime
}
}
dependencies {
androidCompile "some.android:dependency"
desktopCompile "other.desktop:dependency"
}

How to recursively add a list of Jars to your classpath in gradle with groovy script [duplicate]

I have tried to add my local .jar file dependency to my build.gradle file:
apply plugin: 'java'
sourceSets {
main {
java {
srcDir 'src/model'
}
}
}
dependencies {
runtime files('libs/mnist-tools.jar', 'libs/gson-2.2.4.jar')
runtime fileTree(dir: 'libs', include: '*.jar')
}
And you can see that I added the .jar files into the referencedLibraries folder here: https://github.com/WalnutiQ/wAlnut/tree/version-2.3.1/referencedLibraries
But the problem is that when I run the command: gradle build on the command line I get the following error:
error: package com.google.gson does not exist
import com.google.gson.Gson;
Here is my entire repo: https://github.com/WalnutiQ/wAlnut/tree/version-2.3.1
According to the documentation, use a relative path for a local jar dependency as follows.
Groovy syntax:
dependencies {
implementation files('libs/something_local.jar')
}
Kotlin syntax:
dependencies {
implementation(files("libs/something_local.jar"))
}
If you really need to take that .jar from a local directory,
Add next to your module gradle (Not the app gradle file):
repositories {
flatDir {
dirs("libs")
}
}
dependencies {
implementation("gson-2.2.4")
}
However, being a standard .jar in an actual maven repository, why don't you try this?
repositories {
mavenCentral()
}
dependencies {
implementation("com.google.code.gson:gson:2.2.4")
}
You could also do this which would include all JARs in the local repository. This way you wouldn't have to specify it every time.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
The following works for me:
compile fileTree(dir: 'libs', include: '*.jar')
Refer to the Gradle Documentation.
You can try reusing your local Maven repository for Gradle:
Install the jar into your local Maven repository:
mvn install:install-file -Dfile=utility.jar -DgroupId=com.company -DartifactId=utility -Dversion=0.0.1 -Dpackaging=jar
Check that you have the jar installed into your ~/.m2/ local Maven repository
Enable your local Maven repository in your build.gradle file:
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
implementation ("com.company:utility:0.0.1")
}
Now you should have the jar enabled for implementation in your project
A solution for those using Kotlin DSL
The solutions added so far are great for the OP, but can't be used with Kotlin DSL without first translating them. Here's an example of how I added a local .JAR to my build using Kotlin DSL:
dependencies {
compile(files("/path/to/file.jar"))
testCompile(files("/path/to/file.jar"))
testCompile("junit", "junit", "4.12")
}
Remember that if you're using Windows, your backslashes will have to be escaped:
...
compile(files("C:\\path\\to\\file.jar"))
...
And also remember that quotation marks have to be double quotes, not single quotes.
Edit for 2020:
Gradle updates have deprecated compile and testCompile in favor of implementation and testImplementation. So the above dependency block would look like this for current Gradle versions:
dependencies {
implementation(files("/path/to/file.jar"))
testImplementation(files("/path/to/file.jar"))
testImplementation("junit", "junit", "4.12")
}
The accepted answer is good, however, I would have needed various library configurations within my multi-project Gradle build to use the same 3rd-party Java library.
Adding '$rootProject.projectDir' to the 'dir' path element within my 'allprojects' closure meant each sub-project referenced the same 'libs' directory, and not a version local to that sub-project:
//gradle.build snippet
allprojects {
...
repositories {
//All sub-projects will now refer to the same 'libs' directory
flatDir {
dirs "$rootProject.projectDir/libs"
}
mavenCentral()
}
...
}
EDIT by Quizzie: changed "${rootProject.projectDir}" to "$rootProject.projectDir" (works in the newest Gradle version).
Shorter version:
dependencies {
implementation fileTree('lib')
}
The Question already has been answered in detail. I still want to add something that seems very surprising to me:
The "gradle dependencies" task does not list any file dependencies. Even though you might think so, as they have been specified in the "dependencies" block after all..
So don't rely on the output of this to check whether your referenced local lib files are working correctly.
A simple way to do this is
compile fileTree(include: ['*.jar'], dir: 'libs')
it will compile all the .jar files in your libs directory in App.
Some more ways to add local library files using Kotlin DSL (build.gradle.kts):
implementation(
files(
"libs/library-1.jar",
"libs/library-2.jar",
"$rootDir/foo/my-other-library.jar"
)
)
implementation(
fileTree("libs/") {
// You can add as many include or exclude calls as you want
include("*.jar")
include("another-library.aar") // Some Android libraries are in AAR format
exclude("bad-library.jar")
}
)
implementation(
fileTree(
"dir" to "libs/",
// Here, instead of repeating include or exclude, assign a list of paths
"include" to "*.jar",
"exclude" to listOf("bad-library-1.jar", "bad-library-2.jar")
)
)
The above code assumes that the library files are in libs/ directory of the module (by module I mean the directory where this build.gradle.kts is located).
You can use Ant patterns in includes and excludes as shown above.
See Gradle documentations for more information about file dependencies.
Thanks to this post for providing a helpful answer.
I couldn't get the suggestion above at https://stackoverflow.com/a/20956456/1019307 to work. This worked for me though. For a file secondstring-20030401.jar that I stored in a libs/ directory in the root of the project:
repositories {
mavenCentral()
// Not everything is available in a Maven/Gradle repository. Use a local 'libs/' directory for these.
flatDir {
dirs 'libs'
}
}
...
compile name: 'secondstring-20030401'
The best way to do it is to add this in your build.gradle file and hit the sync option
dependency{
compile files('path.jar')
}
The solution which worked for me is the usage of fileTree in build.gradle file.
Keep the .jar which need to add as dependency in libs folder. The give the below code in dependenices block in build.gradle:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
You can add jar doing:
For gradle just put following code in build.gradle:
dependencies {
...
compile fileTree(dir: 'lib', includes: ['suitetalk-*0.jar'])
...
}
and for maven just follow steps:
For Intellij:
File->project structure->modules->dependency tab-> click on + sign-> jar and dependency->select jars you want to import-> ok-> apply(if visible)->ok
Remember that if you got any java.lang.NoClassDefFoundError: Could not initialize class exception at runtime this means that dependencies in jar not installed for that you have to add all dependecies in parent project.
For Gradle version 7.4 with Groovy build file
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation ':gson-2.2.4'
}
If you are on gradle 4.10 or newer:
implementation fileTree(dir: 'libs', includes: ['*.jar'])
Goto File -> Project Structure -> Modules -> app -> Dependencies Tab -> Click on +(button) -> Select File Dependency - > Select jar file in the lib folder
This steps will automatically add your dependency to gralde
Very Simple
Be careful if you are using continuous integration, you must add your libraries in the same path on your build server.
For this reason, I'd rather add jar to the local repository and, of course, do the same on the build server.
An other way:
Add library in the tree view. Right click on this one. Select menu "Add As Library".
A dialog appear, let you select module. OK and it's done.

Multi-project test dependencies with gradle

I have a multi-project configuration and I want to use gradle.
My projects are like this:
Project A
-> src/main/java
-> src/test/java
Project B
-> src/main/java (depends on src/main/java on Project A)
-> src/test/java (depends on src/test/java on Project A)
My Project B build.gradle file is like this:
apply plugin: 'java'
dependencies {
compile project(':ProjectA')
}
The task compileJava work great but the compileTestJava does not compile the test file from Project A.
Deprecated - For Gradle 5.6 and above use this answer.
In Project B, you just need to add a testCompile dependency:
dependencies {
...
testCompile project(':A').sourceSets.test.output
}
Tested with Gradle 1.7.
This is now supported as a first class feature in Gradle. Modules with java or java-library plugins can also include a java-test-fixtures plugin which exposes helper classes and resources to be consumed with testFixtures helper. Benefit of this approach against artifacts and classifiers are:
proper dependency management (implementation/api)
nice separation from test code (separate source set)
no need to filter out test classes to expose only utilities
maintained by Gradle
Example
:modul:one
modul/one/build.gradle
plugins {
id "java-library" // or "java"
id "java-test-fixtures"
}
modul/one/src/testFixtures/java/com/example/Helper.java
package com.example;
public class Helper {}
:modul:other
modul/other/build.gradle
plugins {
id "java" // or "java-library"
}
dependencies {
testImplementation(testFixtures(project(":modul:one")))
}
modul/other/src/test/java/com/example/other/SomeTest.java
package com.example.other;
import com.example.Helper;
public class SomeTest {
#Test void f() {
new Helper(); // used from :modul:one's testFixtures
}
}
Further reading
For more info, see the documentation:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures
It was added in 5.6:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects
Simple way is to add explicit task dependency in ProjectB:
compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Difficult (but more clear) way is to create additional artifact configuration for ProjectA:
task myTestsJar(type: Jar) {
// pack whatever you need...
}
configurations {
testArtifacts
}
artifacts {
testArtifacts myTestsJar
}
and add the testCompile dependency for ProjectB
apply plugin: 'java'
dependencies {
compile project(':ProjectA')
testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}
I've come across this problem myself recently, and man is this a tough issues to find answers for.
The mistake you are making is thinking that a project should export its test elements in the same way that it exports its primary artifacts and dependencies.
What I had a lot more success with personally was making a new project in Gradle. In your example, I would name it
Project A_Test
-> src/main/java
I would put into the src/main/java the files that you currently have in Project A/src/test/java. Make any testCompile dependencies of your Project A compile dependencies of Project A_Test.
Then make Project A_Test a testCompile dependency of Project B.
It's not logical when you come at it from the perspective of the author of both projects, but I think it makes a lot of sense when you think about projects like junit and scalatest (and others. Even though those frameworks are testing-related, they are not considered part of the "test" targets within their own frameworks - they produce primary artifacts that other projects just happen to use within their test configuration. You just want to follow that same pattern.
Trying to do the other answers listed here did not work for me personally (using Gradle 1.9), but I've found that the pattern I describe here is a cleaner solution anyway.
I know it's an old question but I just had the same problem and spent some time figuring out what is going on. I'm using Gradle 1.9. All changes should be in ProjectB's build.gradle
To use test classes from ProjectA in tests of ProjectB:
testCompile files(project(':ProjectA').sourceSets.test.output.classesDir)
To make sure that sourceSets property is available for ProjectA:
evaluationDependsOn(':ProjectA')
To make sure test classes from ProjectA are actually there, when you compile ProjectB:
compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Please read the update bellow.
Similar problems described by JustACluelessNewbie occurs in IntelliJ IDEA. Problem is that dependency testCompile project(':core').sourceSets.test.output actually means: "depend on classes generated by gradle build task". So if you open clean project where classes are not generated yet IDEA won't recognise them and reports error.
To fix this problem you have to add a dependency on test source files next to dependency on compiled classes.
// First dependency is for IDEA
testCompileOnly files { project(':core').sourceSets.test.java.srcDirs }
// Second is for Gradle
testCompile project(':core').sourceSets.test.output
You can observe dependencies recognised by IDEA in Module Settings -> Dependencies (test scope).
Btw. this is not nice solution so refactoring is worth considering. Gradle itself does have special subproject containing test-support classes only. See https://docs.gradle.org/current/userguide/test_kit.html
Update 2016-06-05
More I am thinking about proposed solution less I like it. There are few problems with it:
It creates two dependencies in IDEA. One points to test sources another to compiled classes. And it is crucial in which order these dependencies are recognised by IDEA. You can play with it by changing dependency order in Module settings -> Dependencies tab.
By declaring these dependencies you are unnecessarily polluting dependency structure.
So what's the better solution? In my opinion it's creating new custom source set and putting shared classes into it. Actually authors of Gradle project did it by creating testFixtures source set.
To do it you just have to:
Create source set and add necessary configurations. Check this script plugin used in Gradle project: https://github.com/gradle/gradle/blob/v4.0.0/gradle/testFixtures.gradle
Declare proper dependency in dependent project:
dependencies {
testCompile project(path: ':module-with-shared-classes', configuration: 'testFixturesUsageCompile')
}
Import Gradle project to IDEA and use the "create separate module per source set" option while importing.
New testJar based (trnsitive dependancies supported) solution available as gradle plugin:
https://github.com/hauner/gradle-plugins/tree/master/jartest
https://plugins.gradle.org/plugin/com.github.hauner.jarTest/1.0
From documentation
In case you have a multi-project gradle build you may have test
dependencies between sub-projects (which probably is a hint that your
projects are not well structured).
For example assume a project where the sub-project Project B depends
on Project A and B does not only have a compile dependency on A but
also a test dependency. To compile and run the tests of B we need some
test helper classes from A.
By default gradle does not create a jar artifact from the test build
output of a project.
This plugin adds a testArchives configuration (based on testCompile)
and a jarTest task to create a jar from the test source set (with the
classifier test added to name of the jar). We can then depend in B on
the testArchives configuration of A (which will also include the
transitive dependencies of A).
In A we would add the plugin to build.gradle:
apply plugin: 'com.github.hauner.jarTest'
In B we reference the
testArchives configuration like this:
dependencies {
...
testCompile project (path: ':ProjectA', configuration: 'testArchives')
}
The Fesler's solution haven't worked for me, when i tried it to build an android project (gradle 2.2.0).
So i had to reference required classes manually :
android {
sourceSets {
androidTest {
java.srcDir project(':A').file("src/androidTest/java")
}
test {
java.srcDir project(':A').file("src/test/java")
}
}
}
Here if you are using Kotlin DSL, you should create your task like that according to Gradle documentation.
Like some previous answer, you need to create a special configuration inside the project that will share its tests class, so that you don't mix test and main classes.
Simple steps
In project A you would need to add in your build.gradle.kts :
configurations {
create("test")
}
tasks.register<Jar>("testArchive") {
archiveBaseName.set("ProjectA-test")
from(project.the<SourceSetContainer>()["test"].output)
}
artifacts {
add("test", tasks["testArchive"])
}
Then in your project B in the dependencies, you will need to add in your build.gradle.kts:
dependencies {
implementation(project(":ProjectA"))
testImplementation(project(":ProjectA", "test"))
}
I'm so late to the party (it is now Gradle v4.4) but for anyone else who finds this:
Assuming:
~/allProjects
|
|-/ProjectA/module-a/src/test/java
|
|-/ProjectB/module-b/src/test/java
Go to the build.gradle of project B (the one that needs some test classes from A) and add the following:
sourceSets {
String sharedTestDir = "${projectDir}"+'/module-b/src/test/java'
test {
java.srcDir sharedTestDir
}
}
or (assuming your project is named ProjectB)
sourceSets {
String sharedTestDir = project(':ProjectB').file("module-b/src/test/java")
test {
java.srcDir sharedTestDir
}
}
Voila!
Creating test-jar For Gradle 6.6.x
I know that there are many sources telling you, that is not OK, fe:
https://github.com/gradle/gradle/issues/11280
https://gradle.org/whats-new/gradle-6/#better-builds
But this is so damn simple and I just don't like the idea of having common test classes separately in testFixtures folder.
So in module A:
task jarTests(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
from sourceSets.test.output
}
configurations {
tests {
extendsFrom testRuntime
}
}
artifacts {
tests jarTests
}
And in module B:
testImplementation project(':moduleA')
testImplementation project(path: ':moduleA', configuration: 'tests')
And it just works!
If you want to use artifact dependencies to have:
ProjectB's source classes depend on Project A's source classes
ProjectB's test classes depend on Project A's test classes
then ProjectB's dependencies section in build.gradle should look something like this:
dependencies {
compile("com.example:projecta:1.0.0")
testCompile("com.example:projecta:1.0.0:tests")
}
For this to work ProjectA needs to build a -tests jar and include it in the artifacts it produces.
ProjectA's build.gradle should contain configuration like this:
task testsJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
from sourceSets.test.output
}
configurations {
tests
}
artifacts {
tests testsJar
archives testsJar
}
jar.finalizedBy(testsJar)
When ProjectA's artifacts are published to your artifactory they will include a -tests jar.
The testCompile in ProjectB's dependencies section will bring in the classes in the -tests jar.
If you want to includeFlat ProjectA's source and test classes in ProjectB for development purposes then the dependencies section in ProjectB's build.gradle would look like this:
dependencies {
compile project(':projecta')
testCompile project(path: ':projecta', configuration: 'tests')
}
If you have mock dependencies which you need to share between tests, you can create new project projectA-mock and then add it as test dependency to ProjectA and ProjectB:
dependencies {
testCompile project(':projectA-mock')
}
This is clear solution to share mock dependencies, but if you need to run tests from ProjectA in ProjectB use other solution.
The solution mentioned by Nikita for Android + Kotlin looks like this:
task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
getArchiveClassifier().set('tests')
from "$buildDir/tmp/kotlin-classes/debugUnitTest"
}
configurations {
unitTestArtifact
}
artifacts {
unitTestArtifact jarTests
}
Gradle for project that is going to use dependencies:
testImplementation project(path: ':shared', configuration: 'unitTestArtifact')
If you are struggling to adapt the solution to the Gradle Kotlin DSL this is the equivalent:
configurations {
register("testClasses") {
extendsFrom(testImplementation.get())
}
}
val testJar = tasks.register<Jar>("testJar") {
archiveClassifier.set("test")
from(sourceSets.test)
}
artifacts.add("testClasses", testJar)
Some of the other answers caused errors one way or another - Gradle did not detect test classes from other projects or Eclipse project had invalid dependencies when imported. If anyone has the same problem, I suggest going with:
testCompile project(':core')
testCompile files(project(':core').sourceSets.test.output.classesDir)
The first line forces the Eclipse to link the other project as dependency, so all sources are included and up to date. The second allows Gradle to actually see the sources, while not causing any invalid dependency errors like testCompile project(':core').sourceSets.test.output does.
in project B:
dependencies {
testCompile project(':projectA').sourceSets.test.output
}
Seems to work in 1.7-rc-2

Categories