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()
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 have a project with the following structure:
Root project 'ExampleProject'
+--- Project ':client'
| +--- Project ':client:android'
| +--- Project ':client:core'
| +--- Project ':client:desktop'
| +--- Project ':client:html'
| \--- Project ':client:ios'
+--- Project ':common'
+--- Project ':editor'
\--- Project ':server'
There is one settings.gradle like so:
include ":client", ":common", ":editor", ":server",
"client:desktop", ':client:android', ':client:ios',
':client:html', ':client:core'
rootProject.name = "ExampleProject"
There are build.gradle files at every level. There is one at the project root, one at the client root, one at client:android root, etc.
It appears that the client:android is not inheriting plugins,etc from the build.gradle for the :client project. Is there a maximum (of 2?) of how many build.gradle files can be "chained" and inherited from?
The client build.gradle has a block like this that is not being inherited:
project(":client:android") { //plugins and dependencies here
}
There is no special parent-child relationship in gradle other than the root project and all other modules. Meaning there is no core concept of a subproject that is also a "parent" to it's own subprojects. The documentation seems a little misleading in that respect. It says: "Properties and methods declared in a project are inherited to all its subprojects." But I think this really only relates to the root build.gradle and all others, it is not recursive. Maybe that already answers your question.
Why inheritance is not desired
It is also generally undesirable to have any such concept, because gradle tries to be as fast as possible by being modular and incremental. So build.gradle files should be self-sufficient without needing any parent to run (other than the root build.gradle). This is a fundamental difference to Maven, which relies heavily on inheritance to avoid duplicating XML code.
So while you could write custom gradle code to achieve this kind of inheritance, you probably should not. Instead to share gradle code, you should prefer composition (apply from), add custom code/custom plugins in the buildSrc folder, or define submodule groups in the root build.gradle file.
Workaround for this kind of project setup
So what several gradle projects would do is to define all subproject groups and their configuration in the root build.gradle file like so:
def clientProjects() {
return subprojects.findAll {
// some predicate that is true only for client projects
}
}
configure(clientProjects()) {
apply plugin: ...
}
I have two projects (in a single git repository) that should have the same
repository {
}
section in their build.gradle.kts, but otherwise are completely unrelated.
Can I factor this common part out and include it in each respective build.gradle.kts? How?
Update In the 0.11.0 release, applyFrom(uri) was removed.
You should now instead use:
apply {
from("dir/myfile.gradle")
}
Old answer
With Groovy build scripts you can do something like apply from: 'dir/myfile.gradle' where dir/myfile.gradle is a file containing your shared repositories block.
In a similar fashion with Gradle Script Kotlin (at least with 0.4.1), you can use the applyFrom(script: Any) method.
build.gradle.kts
applyFrom("dir/myfile.gradle")
If you need to apply it from a subproject you could do something like:
applyFrom("${rootProject.rootDir}/dir/myfile.gradle")
No idea if it works with kotlin however you can try equivalent from plain gradle:
lol.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
build.gradle
apply from: 'lol.gradle'
Above works fine. Mind that lol.gradle has java plugin applied - it adds context where repositories is present hence can be applied.
We use an init script bundled in a custom gradle distribution to apply our corporate Nexus repository to every gradle project. It's worth considering if you have a lot of projects.
I encountered a similar problem when common config is replicated in each and every project. Solved it by a custom gradle distribution with the common settings defined in init script.
Created a gradle plugin for preparing such custom distributions - custom-gradle-dist. It works perfectly for my projects, e.g. a build.gradle for a library project looks like this (this is a complete file, all repository, plugin, common dependencies etc are defined in the custom init script):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}
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.