I am attempting to compile several WAR files, all that depend on a common JAR module. In my Gradle build however, I cannot seem to get a 'Provided' like dependency to work with the Java plugin.
My compile looks like this:
apply plugin: 'java'
configurations{
providedCompile
}
dependencies {
compile module("org.springframework.amqp:spring-amqp:${springAmqpVersion}")
compile module("org.slf4j:slf4j-api:${slf4jVersion}")
compile module("org.slf4j:slf4j-ext:${slf4jVersion}")
providedCompile "javax.servlet:servlet-api:${servletApiVersion}"
runtime module("org.slf4j:jcl-over-slf4j:${slf4jVersion}")
runtime module("org.slf4j:jul-to-slf4j:${slf4jVersion}")
runtime module("org.slf4j:log4j-over-slf4j:${slf4jVersion}")
sourceArchives module("org.springframework.amqp:spring-amqp:${springAmqpVersion}:sources")
sourceArchives module("javax.servlet:servlet-api:${servletApiVersion}:sources")
}
sourceSets {
main { compileClasspath += configurations.providedCompile }
}
However, that last bit is where it gets an exception. I have tried adding the servlet-api (Provided by Tomcat) to the configuration after the runtime dependencies would extend it, or simply putting it in as a compile module, then removing it from runtime dependencies later.
I've attempted several different ways of modifying the dependencies, with my closest results being:
newRuntime = configurations.runtime.minus(configurations.providedCompile)
configurations.runtime = newRuntime
This last bit however, will generate the variable newRuntime with the proper dependencies, however when I tried to reassign the variable back to the runtime configuration, it throws a "Cannot find property exception"
I found a lot of discussion of this exact problem on Gradle's bug tracking:Gradle-784
However the main lead from that is from Spring who uses Maven with their gradle builds, which I am unfamiliar with.
The most promising link I found here on SO, but unfortunately I could not get the examples to work as well: SO Provided Question
Of note for the Stack Overflow question the line that showed most promise:
//Include provided for compilation
sourceSets.main.compileClasspath += configurations.provided
This line does not give an error like other attempts, however it appears that the providedCompile (My version of provided) dependency is not actually put on the compile classpath, as there is a classpath error when compilation is attempted.
I'm not 100% following your message but providedCompile is only allowed for 'war' plugin.
apply plugin: 'war'
dependencies {
// others go here
providedCompile "javax.servlet:javax.servlet-api:${servletVersion}"
}
During 'war' step the servlet jar is not included.
You have added a providedCompile configuration, but you aren't doing anything with it. Hence it won't make it on any class path. To put the configuration on the main compile class path, you can do:
sourceSets.main.compileClasspath += configurations.providedCompile
Likewise, to put it on the test compile class path:
sourceSets.test.compileClasspath += configurations.providedCompile
You can use compile scope inside 'jar' modules and providedCompile inside 'war' module.
War's providedCompile scope will override jar's compile scope.
Related
I have build.gradle in front of me and there are some dependencies declared as provided but in documentation I do not see this dependency scope.
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:1.2.4.RELEASE")
....
provided 'backport-util-concurrent:backport-util-concurrent:3.1'
provided 'org.javolution:javolution:5.5.1#jar
....
}
Is this provided by a plugin? If so how do I found out which plugin this belongs to?
What is the difference between provided and runtime dependency scope in Gradle?
What is provided scope?
Suppose that a jar is needed to compile your code, but the jar is present in the production environment library collection. Then you don't need to package the jar with your project archives. To support this requirement, Maven has a scope named provided. If you declare any jar dependency as provided, then this jar will be present in your classpath during compilation but will not be packaged with your project archive.
provided scope is very useful, particularly in web applications. For example, servlet-api.jar is needed to be present in your classpath to compile your project, but you don't need this to package servlet-api.jar file with your war. With provided scope one can achieve this requirement.
There is no Scope defined in Gradle java plugin named provided. Also not in war or android plugins. If you want to use provided scope in your project, then you have to define it in your build.gradle file. Following is the code snippet to declare provided scope in gradle:
configurations {
provided
}
sourceSets {
main { compileClasspath += configurations.provided }
}
Now, your second question:
What is the difference between provided and runtime dependency scope in Gradle?
To answer this question first I will define compile dependency. compile dependencies are dependencies, those are necessary to compile your code. Now imagine that if your code uses a library named X then you must declare X as your compile-time dependency. Also imagine that X uses another library Y internally, and you declared Y as your runtime dependency.
During compilation, Gradle will add X into your classpath but will not add Y. Since, Y is not required for compilation. But it will package both X and Y with your project archive since both X and Y are necessary to run your project archive in the production environment. Generally, all the dependencies needed in the production environment are known as runtime dependency.
In Gradle official documentation, it says that runtime dependency are "the dependencies required by the production classes at runtime. By default, also includes the compile time dependencies.".
Now, if you've read this far, then you already know that provided is a compile dependency that we don't want to be present in the runtime dependency (basically, we don't want it to package with the project archive).
Following is an illustration of provided and runtime scope. Here, compile refers to the dependencies that are required to compile the project and non-compile refers to the dependencies that are not required for project compilation.
As from Gradle 2.12, you can use the compileOnly option.
See
https://blog.gradle.org/introducing-compile-only-dependencies
For further clarification, as of the latest version, Gradle 5.5 has compileOnly (same as provided) and runtimeOnly options. The new default "compile and runtime" option is implementation.
Updating the answer as per the latest gradle versions.
From gradle's official documentation at below link:
https://docs.gradle.org/current/userguide/upgrading_version_5.html
Deprecations
Dependencies should no longer be declared using the compile and runtime configurations. The usage of the compile and runtime
configurations in the Java ecosystem plugins has been discouraged
since Gradle 3.4.
The implementation, api, compileOnly and runtimeOnly configurations should be used to declare dependencies and the compileClasspath and
runtimeClasspath configurations to resolve dependencies.
More so, the compile dependency configuration has been removed in the recently released Gradle 7.0 version.
If you try to use compile in your Gradle 3.4+ project you’ll get a warning like this:
Deprecated Gradle features were used in this build, making it
incompatible with Gradle 7.0. Use ‘–warning-mode all’ to show the
individual deprecation warnings.
You should always use implementation rather than compile for dependencies, and use runtimeOnly instead of runtime.
War plugin
The War plugin extends the Java plugin to add support for assembling web application WAR files. It disables the default JAR archive
generation of the Java plugin and adds a default WAR archive task.
The War plugin adds two dependency configurations:
providedCompile
providedRuntime
Adding an entry to providedCompile or providedRuntime will cause that dependency to be excluded from the war file.
Use providedCompile if you have source that relies on some classes
for compiling.
Use providedRuntime if you use it for testing and not
compiling.
Example:
providedCompile 'org.springframework.boot:spring-boot-starter-tomcat:1.1.6.RELEASE'
The above JAR and its transitive dependency will only be available at compile time but it will not be available at runtime. It means, those JAR will not be included in war archive.
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.
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"
}
Well, I'm migrating my Android project to use the Clean Architecure:
https://github.com/android10/Android-CleanArchitecture
This means that part of my code is within the domain module (pure Java, no dependency with Android). For this project, I'm using Dagger 2, that generates source using the annotation processor (during compile time).
I have the following Gradle's configuration for my project:
apply plugin: 'java'
sourceCompatibility = 1.7
targetCompatibility = 1.7
configurations {
provided
}
sourceSets {
main {
compileClasspath += configurations.provided
runtimeClasspath += configurations.provided
}
test {
compileClasspath += configurations.provided
runtimeClasspath += configurations.provided
}
}
dependencies {
def domainDependencies = rootProject.ext.domainDependencies
def domainTestDependencies = rootProject.ext.domainTestDependencies
provided domainDependencies.daggerCompiler
provided domainDependencies.javaxAnnotation
compile domainDependencies.dagger
compile domainDependencies.rxJava
compile domainDependencies.joda
testCompile domainTestDependencies.junit
testCompile domainTestDependencies.assertJ
testCompile domainTestDependencies.mockito
testCompile domainTestDependencies.jMockLegacy
testCompile domainTestDependencies.commonsCsv
}
In my test source, I created the interface TestComponent and the Dagger is suposed to generate the DaggerTestComponent. When I try to build my project either through command line or Android Studio, I receive compilation errors of cannot find symbol and then: Execution failed for task ':domain:compileTestJava'.
I tried to change the 'provided' with 'compile' and 'testCompile'. It's still not working.
What is strange is that, after the failure of the compileTestJava, I can find the generated DaggerTestComponent.java in domain/build/classes/test. So, if it's being generated, why am I receiving this compile error?
It's important to note that this problem only happens in the test source. I have generated source of Dagger 2 being used in the main source.
UPDATE:
I commented every place that was trying to use the DaggerTestComponent and tried to build again. In the domain/build/classes/test, now I can find not only the DaggerTestComponent.java but also the .class resulted of the compilation. So, it's generating the source file and compiling it. Why is the compilation of files using it not working? It seems like some order problem, like the generated source isn't ready yet at the time of the compile of the other sources.
Thanks to #EpicPandaForce, I started worndering if there was a APT plugin for pure Java too. After searching, I found this one:
https://github.com/tbroyer/gradle-apt-plugin
I just applied that plugin and changed my dependencies with apt and testApt.
Following the explanation in "Building and Testing with Gradle" I have a multiproject gradle setup like this:
rootFolder
build.gradle
settings.gradle
EMS
build.gradle
cloud-sdk
build.gradle
The cloud-sdk project depends on several jars, partially resolved via maven partially via locale jars:
// file: cloud-sdk/build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile group:'org.apache.tomcat', name:'tomcat-catalina', version:'7.0.47'
compile group:'org.mongodb', name:'mongo-java-driver', version:'2.11.3'
compile group:'com.google.code.gson', name:'gson', version:'2.2.4'
compile group:'com.thoughtworks.xstream', name:'xstream', version:'1.4.6'
compile fileTree(dir:'lib/', include:'JavaPNS_2.2.jar')
compile fileTree(dir:'lib/', include:'gcm-server.jar')
}
The EMS-project depends on the cloud-sdk which I think should be defined like this:
// file: EMS/build.gradle
apply plugin: 'java'
dependencies {
compile project(':cloud-sdk')
}
Furthermore, my root build.gradle and settings.gradle files look like this:
settings.gradle
include 'cloud-sdk', 'EMS'
build.gradle
apply plugin: 'java'
dependencies {
compile project(':EMS')
}
In this case I am not sure whether I also need the dependency compile project (':cloud-sdk'). I tried both version but since I get the same error message in both cases I assume it doesn't matter.
When I try to run the script from the rootFolder via gradle build I get the following error messages:
Could not resolve all dependencies for configuration ':EMS:compile'.
> Could not find org.apache.tomcat:tomcat-catalina:7.0.47.
Required by:
rootFolder:EMS:unspecified > rootFolder:cloud-sdk:unspecified
> Could not find org.mongodb:mongo-java-driver:2.11.3.
Required by:
rootFolder:EMS > rootFolder:cloud-sdk:unspecified
> Could not find com.google.code.gson:gson:2.2.4.
Required by:
rootFolder:EMS > rootFolder:cloud-sdk:unspecified
> Could not find com.thoughtworks.xstream:xstream:1.4.6.
Required by:
rootFolder:EMS:unspecified > rootFolder:cloud-sdk:unspecified
But when I just build the cloud-sdk project via gradle cloud-sdk:build gradle downloads the required jars and builds the project without a problem.
But even if I try gradle build after that, although gradle notices that the cloud-sdk project is already up-to-date, it complains about the missing dependencies.
Why is that? It downloaded them already so they should be available somewhere and even if not the cloud-sdk knows what it needs and how to fetch it. What am I missing? Do I need to specify the dependencies in some other way?
Ok, it turns out gradle could not fetch the dependencies in the EMS project since I did not specify any repositories to fetch them from. I assumed that would not be necessary since the only dependencies I needed it to fetch were declared in the cloud-sdk project and that did have a repository given.
This is basically the solution to my problem, but if anybody can explain to me why it is necessary to specify the repository again or explain why it is a bug in gradle that should be fixed, I'll accept that answer as it would answer the "why" and not just the "how do I get it to work".