I have a multi project that has a structure like this:
.
├── all
├── left
└── right
I am trying to make :all a "composite meta project" of it's siblings (:left and :right). That is :all project should publish just a pom that declares dependency on siblings (it itself contains no code and doesn't produce a jar).
Is this possible? And if so how should I configure :all to achieve it?
I am using gradle 1.12
I've prepared the following example. Maybe I it will help You.
root/build.gradle is empty
root/settings.gradle
include ":l", ":r", ":all"
root/l/build.gradle is empty
root/p/build.gradle is empty
root/all/build.gradle
apply plugin: 'java'
dependencies {
compile project(':l')
compile project(':r')
}
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
}
Hope it helps. Mind that this isn't a full example, rather proof of concept.
Related
I'm working on a springboot microservices application using Gradle and I want to have a separate parent module for all the common parts that is going to be used by all of my microservices (abstract entities, common properties, dependencies versions ...). This parent module is going to have it's own repository. I have done something similar when I worked on a maven app, by having a <packaging>pom</packaging> for the parent project.
So my questions are, Am I doing the right thing by separating the common aspects of my application in a separate repository ? and what is the best way to do so in gradle ?
Edit :
In order for me to be more precise about my problematic, I want to do what is described in this approach using Gradle instead of maven.
https://stackoverflow.com/a/27865893/8326336
Thank you for your help.
Personally I would try to reuse only complex parts, or complex tasks/plugins if really needed. There is a maintenance cost with "parent logic" especially for things that change often and a lot flexibility can be lost. Also updating multiple dependent projects is not fun. So be careful.
With Gradle it's possible to reuse some common build logic. One way of doing this is to create a convention Gradle project. I will use Kotlin dsl in examples, but same thing can be done with groovy.
Convention project
First create a normal Gradle project and put in build.gradle.kts config like:
plugins {
// Kotlin dsl plugin since we will use Kotlin dsl
// (you can also use Groovy version if you like Groovy)
`kotlin-dsl`
// Plugin needed to publish it
id("maven-publish")
}
repositories {
// This repository is needed for getting kotlin-gradle-plugin,
// you can also add any other repo here if you add any other dep.
gradlePluginPortal()
}
dependencies {
// This is needed so we can access gradle constructs
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30")
}
publishing {
// Config to publish it
publications {
create<MavenPublication>("maven") {
group = "com.mycompany"
artifactId = "gradle-conventions"
version = "1.0.0"
from(components["java"])
}
}
repositories {
// my repositories where I want publish this
}
}
Where to put common logic? You put it in src/main/kotlin (or groovy if you use groovy). So lets create such structure:
└── src
└── main
└── kotlin
├── dependencies
│ └── CommonDependencies.kt
├── my-company.java-conventions.gradle.kts
Where CommonDependenies.kt has our dependencies:
package dependencies
open class CommonDependencies {
val guava = "com.google.guava:guava:30.1.1-jre"
}
and my-company.java-conventions.gradle.kts has our common Java settings:
import dependencies.CommonDependencies
plugins {
id("java-library")
}
// register extension so we can nicely access variables from CommonDependencies
extensions.create<CommonDependencies>("commonLibs")
java {
// All our projects will use toolchains with Java11
toolchain.languageVersion.set(JavaLanguageVersion.of("11"))
}
tasks.test {
// All our projects use Junit
useJUnitPlatform()
}
Now since this is a regular project you can publish it to maven repo. For testing purposes, let's publish it to local maven repo with:
./gradlew publishToMavenLocal
Ok, our conventions are all set. Now how we can use them?
Consumer project
In consumer project we have to add our project to build logic classpath. This can be done by adding our project to buildSrc/build.gradle(.kts) or to buildScript. Lets for example put it into buildSrc.
buildSrc/build.gradle.kts example:
repositories {
// I have put maven local here just because I published
// convention project to maven local
mavenLocal()
gradlePluginPortal()
}
dependencies {
implementation("com.mycompany:gradle-conventions:1.0.0")
}
And after this is set, IDE reloaded, you can use your conventions in your modules. Example:
plugins {
id("my-company.java-conventions")
}
dependencies {
implementation(commonLibs.guava)
}
Notes
If you don't want to publish your conventions to some Maven repo, you can also just include project locally with includeBuild(). For example if you have projects in same folders like that:
├── gradle-conventions
├── gradle-project-consuming-conventions
You would do in settings.gradle(.kts) of gradle-project-consuming-convention: includeBuild("../gradle-conventions")
In my-company.java-conventions.gradle.kts you can skip my-company. Name it however you think is best, just be careful that it does not conflict with official plugins.
I used Gradle 7.2
This is definitely a beginner gradle question, but I just can't make it work.
I have a library and an application which depends on the library.
.
/ library
/ application
I can install the library through gradle install in ./library, then have the application depend on the library through the group/name/version, and pick up the library through the mavenLocal() repository in the application build.gradle, then build the app in the application folder. And that works.
But I'd like to have a project, and that I could have a single command to build the application and, if needed, the library too (similar to the maven -am flag).
Here's what I have right now, in the parent folder, build.gradle:
subprojects {
apply plugin: "java"
repositories {
mavenCentral()
}
}
project(':application') {
dependencies {
compile project(':library')
}
}
and in the same folder I have settings.gradle:
include ':library', ':application'
Again, if I go in the library folder and run gradle install, it works. I get the artifact in my ~/.m2.
But now with the configuration I described, if I go in the root folder and run gradle shadowJar.. and the shadowJar task is present only in application, then gradle tries to compile the library but fails, apparently because it doesn't pick up the library dependencies.
* What went wrong:
Execution failed for task ':library:compileJava'.
> Compilation failed; see the compiler error output for details.
the errors are like, for instance:
Task :library:compileJava FAILED
.../DateTimeAdapter.java:5: error: package javax.xml.bind.annotation.adapters does not exist
import javax.xml.bind.annotation.adapters.XmlAdapter;
and sure enough these are dependencies in the library/build.gradle =>
implementation 'com.fasterxml.jackson.core:jackson-core:2.8.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.8'
So my question is how do I specify the application dependency on library, so that I can run a single command and have maven build the library if needed, then the application.. I thought what I did would have gotten the first part through, but it fails, apparently due to library's transitive dependencies. I actually expect I need to handle a second part after that, which is how to specify the dependency in the application build.json. I may have to switch from specifying the maven group/name/version coordinates to the project name, but I didn't get that far yet.
I would definitely not want to list the library dependencies in the root gradle file: I'd like library to handle its dependencies, and application its dependencies, I'd like to keep the root gradle file small.
application is unable to see the dependencies of library because you have declared the Jackson dependencies as an implementation detail of library.
What you intended to do was expose your library and it's dependencies as an api for consumers.
Dependencies declared in the implementation configuration can be thought of as "private" meaning consumers of your library should not access methods/classes that use those dependencies otherwise they will face errors like the one you are. api is basically the opposite of implementation.
The api configuration is available via the java-library plugin.
Full working example for what you're trying to achieve (Kotlin DSL):
├── application
│ └── build.gradle.kts
├── build.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
│ └── build.gradle.kts
└── settings.gradle.kts
Root project build.gradle.kts:
subprojects {
apply {
plugin("java-library")
}
repositories {
jcenter()
}
}
settings.gradle.kts:
rootProject.name = "example-proj"
include("application")
include("library")
Library build.gradle.kts:
dependencies {
api("com.fasterxml.jackson.core:jackson-core:2.10.0")
api("com.fasterxml.jackson.core:jackson-databind:2.10.0")
}
Application build.gradle.kts:
dependencies {
implementation(project(":library"))
}
I built a small little test project to see if Gradle would solve this problem we currently have with Maven. We have 200 little libraries, all of them Maven projects, whenever you do a clean checkout, you have to mvn install each of them individually
To simulate such a scenario, i've created 4 modules:
root
- jvaas-gson
- jvaas-jackson
- jvaas-json
- jvaas-provider
Both jvaas-gson and jvaas-jackson depends on jvaas-json and jvaas-provider. jvaas-json only depends on jvaas-provider.
If some external application wants to include JSON capabilities, they should only have to include jvaas-gson or jvaas-jackson which uses an interface in jvaas-json (as i said, this is just experimenting with it, actual use-case would be to switch out email providers, payment providers etc only having to change a line in the Gradle build script)
In jvaas-provider i have a settings.gradle.kts
rootProject.name = "jvaas-provider"
and a build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
group = "io.jvaas"
version = "1.3.0"
plugins {
`maven-publish`
kotlin("jvm") version("1.3.10")
id("org.jetbrains.dokka") version "0.9.16"
}
repositories {
jcenter()
}
dependencies {
implementation(kotlin("stdlib", getKotlinPluginVersion()))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
}
publishing {
repositories {
mavenLocal()
}
}
In jvaas-json i'm trying to access one of the classes in jvaas-provider, so i've added it to the settings.gradle.kts
rootProject.name = "jvaas-json"
include("jvaas-provider")
and the build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
group = "io.jvaas"
version = "1.3.0"
plugins {
`maven-publish`
kotlin("jvm") version("1.3.10")
id("org.jetbrains.dokka") version "0.9.16"
}
repositories {
jcenter()
}
dependencies {
implementation(kotlin("stdlib", getKotlinPluginVersion()))
implementation(group = "io.jvaas", name = "jvaas-provider", version = "$version")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
}
Inside jvaas-json, i'm trying access one of the classes i created inside jvaas-provider, but it's not resolving.
Without having to manually install jvaas-provider in the local maven repo (mvn install), is it possible to use it as a dependency inside jvaas-json? If so, what should i be changing in my Gradle build scripts and Settings files?
Paul on the Kotlin Slack just answered my question just before i could hit Post on my StackOverflow question, so posting this as QA-style, maybe it'll help somebody else in the future.
Gradle has something called composite builds which allows you to include other projects / libraries inside your project without having to install them to a local maven repo first.
For the above example to work with composite builds, all i had to do was change something in the settings.gradle.kts file:
rootProject.name = "jvaas-json"
includeBuild("../jvaas-provider")
I had quite good gradle configuration, that built everything just fine. But one of the projects of my multi-project build derived from the rest of them so much, that I would gladly move it to another git repo and configure submodules to handle it.
First, I moved Project and its resources to subfolder Libraries/MovedProject. After altering some lines in gradle configurations it worked fine. But then I decided to write a new build.gradle just for this project, and move all configurations there from the main one.
And this is where everything stopped working. When I try to call any task it always ends
with Could not find property 'sourceSets' on project ':Libraries/MovedProject'. Line which is responsible for it is:
dependencies {
...
if (noEclipseTask) {
testCompile project(':Libraries/MovedLibrary').sourceSets.test.output
}
}
which I use for running tests in which I use classes from other projects. If I remove that line, the build fails only when it reaches compileTestJava task of projects that make use of MovedProject. If I remove that line and call gradle :Libraries/MovedLibrary:properties I can see :
...
sourceCompatibility: 1.7
sourceSets: [source set main, source set test]
standardOutputCapture: org.gradle.logging.internal.DefaultLoggingManager#1e263938
...
while gradle :Libraries/MovedLibrary:build builds correctly.
Currently I've got everything set up as following:
directories:
/SomeMainProject1
/SomeMainProject2
/SomeMainProject3
/Libraries
/MovedProject
build.gradle
dependencies.gradle
project.gradle
tasks.gradle
/Builder
dependencies.gradle
project.gradle
tasks.gradle
build.gradle
settings.gradle
settings.gradle
include Libraries/MovedProject,
SomeMainProject1,
SomeMainProject2,
SomeMainProject3
sourceSets for MovedProject are defined in Libraries/MovedProject/project.gradle:
sourceSets {
main {
java {
srcDir 'src'
srcDir 'resources'
}
resources { srcDir 'resources' }
}
test { java {
srcDir 'test/unit'
} }
}
dependencies that makes use of sourceSets.test.output are stored in Builder/dependancies.gradle, and set for each project that needs MovedProject to run tests:
project(':SomeMainProject1') {
dependencies {
...
if (noEclipseTask) {
testCompile project(':Libraries/net.jsdpu').sourceSets.test.output
}
}
}
What would be the easiest way to get rid of that error and make gradle build projects with current directory structure? I would like to understand why gradle cannot see that property.
The line in question is problematic because it makes the assumption that project :Libraries/MovedLibrary is evaluated (not executed) before the current project, which may not be the case. And if it's not, the source sets of the other project will not have been configured yet. (There won't even be a sourceSets property because the java-base plugin hasn't been applied yet.)
In general, it's best not to reach out into project models of other projects, especially if they aren't children of the current project. In the case of project A using project B's test code, the recommended solution is to have project B expose a test Jar (via an artifacts {} block) that is then consumed by project A.
If you want to keep things as they are, you may be able to work around the problem by using gradle.projectsEvaluated {} or project.evaluationDependsOn(). See the Gradle Build Language Reference for more information.
I had a similar error happen to me in a multimodule project, but for me the cause was as simple as I had forgotten to apply the java-library plugin within the configurations, I only had maven-publish plugin in use.
Once I added the plugin, sourceSets was found normally:
configure(subprojects) {
apply plugin: 'maven-publish'
apply plugin: 'java-library'
....
When applying a multi-project Gradle structure to our project, my settings.gradle looks like this:
include "source:compA:api"
include "source:compA:core"
include "source:compB"
gradle projects give me
Root project 'tmp'
\--- Project ':source'
+--- Project ':source:compA'
| +--- Project ':source:compA:api'
| \--- Project ':source:compA:core'
\--- Project ':source:compB'
This is exactly the directory structure!
In my root directory I have a build.gradle which applies the java plugin to all subprojects:
subprojects {
apply plugin: 'java'
}
When building I end up having artifacts for :source:compA which are empty because this is actually not a project just the subdirectories api and core are proper Java projects.
What's the best way to avoid having an empty artifact?
You can try using the trick they use in Gradle's own settings.gradle file. Note how each of the sub projects are located in the 'subprojects/${projectName}' folder, but the subprojects folder itself is not a project.
So in your case you'd do something like:
include "source:compA-api"
include "source:compA-core"
include "source:compB"
project(':source:compA-api').projectDir = new File(settingsDir, 'source/compA/api')
project(':source:compA-core').projectDir = new File(settingsDir, 'source/compA/core')
I have intentionally omitted the colon between compA and api to make sure source:compA does not get evaluated as a project container.
Alternatively, you can try excluding the source:compA project from having the java plugin applied to it, by doing something like:
def javaProjects() {
return subprojects.findAll { it.name != 'compA' }
}
configure(javaProjects()) {
apply plugin: 'java'
}
Edit:
Alternatively you can try something like this (adjust to your liking):
def javaProjects() {
return subprojects.findAll { new File(it.projectDir, "src").exists() }
}
configure(javaProjects()) {
apply plugin: 'java'
}
Starting with Gradle 6.7, the Gradle user manual recommends against configuring subprojects using the "cross project configuration" feature using subprojects and allprojects:
Another, discouraged, way to share build logic between subproject is cross project configuration via the subprojects {} and allprojects {} DSL constructs. With cross configuration, build logic can be injected into a subproject and this is not obvious when looking at the subproject’s build script, making it harder to understand the logic of a particular subproject. In the long run, cross configuration usually grows complex with more and more conditional logic and a higher maintenance burden. Cross configuration can also introduce configuration-time coupling between projects, which can prevent optimizations like configuration-on-demand from working properly.
The suggested approach is to instead use convention plugins to define the common traits:
Gradle’s recommended way of organizing build logic is to use its plugin system. A plugin should define the type of a subproject. In fact, Gradle core plugins are modeled in the same way - for example, the Java Plugin configures a generic java project, while Java Library Plugin internally applies the Java Plugin and configures aspects specific to a Java library in addition. Similarly, the Application Plugin applies and configures the Java Plugin and the Distribution Plugin.
You can compose custom build logic by applying and configuring both core and external plugins and create custom plugins that define new project types and configure conventions specific to your project or organization. For each of the example traits from the beginning of this section, we can write a plugin that encapsulates the logic common to the subproject of a given type.
We recommend putting source code and tests for the convention plugins in the special buildSrc directory in the root directory of the project. For more information about buildSrc, consult Using buildSrc to organize build logic.
In your particular case, you could follow the approach given in Gradle's sample:
├── buildSrc
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ └── groovy
│ │ │ ├── source.java-conventions.gradle
The buildSrc/build.gradle file would consist of just the groovy-gradle-plugin:
plugins {
id 'groovy-gradle-plugin'
}
The buildSrc/src/main/groovy/source.java-conventions.gradle would contain the common logic for your Java projects. In your example, you just had the application of the Java plugin, but you would add any other commonality of the Java plugins that wouldn't be shared with non-Java projects:
plugins {
id 'groovy-gradle-plugin'
}
Each Java project would then include the convention plugin:
plugins {
id 'source.java-conventions'
}
Note that this doesn't buy much if literally the only thing that's common is the java plugin; you're replacing one plugin inclusion with another. But as soon as you end up with more shared build logic than that, it starts to pay off in terms of cross-project consistency & reduction of duplicated code.
I have the situation two. The empty parent directory is regarded as project. We can have some check to ignore the project.
project.getBuildFile().exists()