Gradle build: how to set up a multilevel project (3+ levels)? - gradle

I am having troubles with seting up 3 level gradle multi-project:
Level_1_Proj
│
├── Level_2_Proj_A
│ │
│ ├── Level_3_Proj_C
│ └── Level_3_Proj_D
│
└── Level_2_Proj_B
│
├── Level_3_Proj_E
└── Level_3_Proj_F
I would like to be able to:
set up dependencies between projects of the same level in the build script, like:
dependencies {
project('Level_2_Proj_A') {
dependencies {
implementation project('Level_2_Proj_B')
}
}
}
also want to be able to build (the subtree) starting the bash command [$gradle build] at any level [then build down the projects' subtree]
I have achieved building from the middle and bottom levels, but I cannot setup the build from the top level. Getting error:
A problem occurred evaluating project ‘Level_2_Proj_A’.
Project with path ‘Level_3_Proj_C’ could not be found in project ‘Level_2_Proj_A’.
Is it possible? If so, how to configure?
Thanks

Alright, here's how I managed to get it working. Given the following directory structure:
.
├── A
│   ├── C
│   │   └── build.gradle.kts
│   ├── D
│   │   └── build.gradle.kts
│   └── build.gradle.kts
├── B
│   ├── E
│   │   └── build.gradle.kts
│   ├── F
│   │   └── build.gradle.kts
│   └── build.gradle.kts
├── build.gradle.kts
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
The settings.gradle.kts has the following content:
rootProject.name = "multi-module-build"
include(":A")
include(":A:C")
include(":A:D")
include(":B")
include(":B:E")
include(":B:F")
Each build.gradle.kts has a call task that prints the name path of the project, e.g.
Inside the C project:
tasks.register("call") {
println(":A:C")
}
Inside the A project:
tasks.register("call") {
dependsOn(":A:C:call")
dependsOn(":A:D:call")
println(":A")
}
tasks["build"].dependsOn(":A:call")
The tasks["build"].dependsOn(":A:call") tells Gradle to invoke :A:call when building. The two dependsOn inside the call definition for A invoke the subproject call tasks.
There is a similar structure available for B.
When running gradle build at root level, this is the output I get:
:A
:B
:A:C
:A:D
:B:E
:B:F
When running gradle build inside the A subproject, I get:
:A
:A:C
:A:D
When running it inside :A:C, I don't get any output because I haven't specified that C's build task should depend on call, but that could easily be done.
Let me know if this doesn't work for you. I've used the Kotlin DSL for gradle, but you're perfectly free to change it to the Groovy variant.

Related

How to include another module in dependency of my gradle project, but exclude certain directories

We have a gradle multi module project. api-1 is a dropwizard legacy project, and api-2 is the spring-boot project.
root-project
├── api-1
│   ├── build.gradle
│   ├── config
│   ├── libs
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   └── resources
│   │   │   ├── META-INF
│   │   │      └── services
│   │   │      └── org.hibernate.integrator.spi.Integrator
├── api-2
│   ├── build.gradle
│   └── src
│   ├── main
│   │   └── resources
│   └── test
│   └── resources
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
We have our entities in api-1 which we are trying to use in api-2. So in api-2's build.gradle file I included implementation project(':api-2'). It feels a lil bit coarse to import the module along with all its transitive dependencies in to this module; but for now I does the trick.
The Issue:
The problem here is that api-1 has hibernate 5.2 dependency and it has this org.hibernate.integrator.spi.Integrator file in the resource folder. It references org.hibernate.jpa.event.spi.JpaIntegrator. Apparently this class was removed from hibernate from version 5.3 (ref: https://github.com/hibernate/hibernate-orm/blob/5.3/migration-guide.adoc#jpaintegrator-removed )
Now when I run (task bootRun) my api-2 module, I it fails with the below exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.util.ServiceConfigurationError: org.hibernate.integrator.spi.Integrator: Provider org.hibernate.jpa.event.spi.JpaIntegrator not found
What I Already Tried:
However if I delete this file (org.hibernate.integrator.spi.Integrator) from api-2, then api-2 runs and also api-1 seems to be running without any issues. But api-1 is a legacy project, and I am wary that it is wreck-less to simply remove the file without understanding it's implication fully.
I am trying to figure out if there is a safer way to get around this issue; maybe exclued that particular file in api-2. Or any better alternative?

Unable to compile code generated using gradle-swagger-generator-plugin

I am facing a problem that code generated using gradle-swagger-generator-plugin is not compiling due to unresolved dependencies.
I have a multimodule gradle project with the following folder structure. parent is the parent project with three subprojects named modules:apis, modules:api-client, and modules:common
.
├── parent
│   ├── build.gradle
│   ├── gbuild
│   ├── gradle
│   │   └── wrapper
│   │   ├── gradle-wrapper.jar
│   │   └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── modules
│   │   ├── apis
│   │   │   ├── build.gradle.kts
│   │   │   ├── gbuild
│   │   │   └── src
│   │   ├── api-client (generated)
│   │   │   ├── README.md
│   │   │   ├── build.gradle
│   │   │   ├── build.sbt
│   │   │   ├── docs
│   │   │   ├── gbuild
│   │   │   ├── git_push.sh
│   │   │   ├── gradle
│   │   │   ├── gradle.properties
│   │   │   ├── gradlew
│   │   │   ├── gradlew.bat
│   │   │   ├── pom.xml
│   │   │   ├── settings.gradle
│   │   │   └── src
│   │   └── common
│   │   ├── build.gradle.kts
│   │   ├── gbuild
│   │   └── src
│   ├── project_version.properties
│   └── settings.gradle.kts
Module :modules:apis is the spring boot REST service for which I want to generate client SDK. Module :modules:api-client is the placeholder project wherein the client code is generated.
Relevant sections from the modules/apis/build.gradle.kts are as given below.
plugins {
// Some plugins omitted for clarity
java
id("org.springframework.boot")
id("io.spring.dependency-management")
id("org.hidetake.swagger.generator") version "2.18.2"
}
dependencies {
// Other dependencies omitted
implementation (Libs.io_swagger_core)
swaggerCodegen("io.swagger.codegen.v3:swagger-codegen-cli:3.0.27")
}
swaggerSources {
create("testcode") {
// File api-spec is already generated and placed at the right location.
setInputFile(file("$projectDir/src/main/resources/swagger/api-spec.json"))
code(
closureOf<org.hidetake.gradle.swagger.generator.GenerateSwaggerCode> {
wipeOutputDir = true
language = "java"
configFile = file("$projectDir/src/main/resources/swagger/api-config.json")
outputDir = file(
"$rootDir/modules/client"
)
}
)
}
}
tasks.named("build") { dependsOn("generateSwaggerCode") }
What works: I can generate client code using gradle clean build in modules\apis and build it using .\gradle.bat clean build or using gradle clean build in modules\api-client.
What does not work: When I build the parent project using .\gradlew build --no-daemon --stacktrace --info in the parent folder, the task :modules:api-client:compileJava fails with compiler errors only for the first run. Compilation fails because gradle fails to resolve dependencies from modules\api-client\build.gradle. Compiler errors can be found in the attached file.
However, subsequent builds using the same command succeed without problem until modules\api-client folder is deleted manually.
Gradle version used by the parent project
\path\to\parent> .\gradlew.bat --version
------------------------------------------------------------
Gradle 6.7
------------------------------------------------------------
Build time: 2020-10-14 16:13:12 UTC
Revision: 312ba9e0f4f8a02d01854d1ed743b79ed996dfd3
Kotlin: 1.3.72
Groovy: 2.5.12
Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM: 1.8.0_181 (Oracle Corporation 25.181-b13)
OS: Windows 10 10.0 amd64
Gradle version used by :modules:api-client subproject
\path\to\parent\modules\api-client> .\gradlew.bat --version
------------------------------------------------------------
Gradle 2.6
------------------------------------------------------------
Build time: 2015-08-10 13:15:06 UTC
Build number: none
Revision: 233bbf8e47c82f72cb898b3e0a96b85d0aad166e
Groovy: 2.3.10
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM: 1.8.0_181 (Oracle Corporation 25.181-b13)
OS: Windows 10 10.0 amd64
I have tried this with all four combinations of options --build-cache and --no-parallel but got the same result. I am relatively new to gradle and took a wild guess that these two options may be playing role in this problem.
Any help in resolving this issue will be highly appreciated.
Thanks
Anand
You should put the generated classes in the buildDir (since you don't want to modifiy them as they get regenerated at each build) and then you need to add the generated class to your sourceSet.
In your build.gradle.kts add this:
configure<SourceSetContainer> {
named("main") {
java.srcDir("$buildDir/generated/src/main/java")
}
}
With the path being the one you specify in your swagger generation task.
Also on my case I set the dependency on the KotlinCompile task:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependsOn("generateSwaggerCode")
kotlinOptions.jvmTarget = "11"
}
Make sure that this "generateSwaggerCode" does generate the code you need though (./gradlew generateSwaggerCode)

Mimicking Multiproject Execution In Composite Included Build with Gradle Kotlin DSL?

I am simplifying the setup I have to illustrate my issue, but have included structural complexities.
Using Gradle's Kotlin DSL I have a composite build where the root project is empty and the two included builds are both side-by-side multiproject builds with varying structures that make use of "container" projects (aka, empty directories with no build.gradle.kts files) for organization purposes.
.
├── app
│   ├── common
│   │   └── build.gradle.kts
│   ├── js
│   │   └── build.gradle.kts
│   ├── jvm
│   │   └── build.gradle.kts
│   ├── build.gradle.kts
│   └── settings.gradle.kts
├── library
│   ├── core
│   │   ├── common
│   │   │   └── build.gradle.kts
│   │   ├── js
│   │   │   └── build.gradle.kts
│   │   └── jvm
│   │   └── build.gradle.kts
│   ├── other-component
│   │   ├── common
│   │   │   └── build.gradle.kts
│   │   ├── js
│   │   │   └── build.gradle.kts
│   │   └── jvm
│   │   └── build.gradle.kts
│   ├── util
│   │   ├── util1
│   │   │   ├── common
│   │   │   │   └── build.gradle.kts
│   │   │   ├── js
│   │   │   │   └── build.gradle.kts
│   │   │   └── jvm
│   │   │   └── build.gradle.kts
│   │   └── util2
│   │   ├── common
│   │   │   └── build.gradle.kts
│   │   ├── js
│   │   │   └── build.gradle.kts
│   │   └── jvm
│   │   └── build.gradle.kts
│   ├── build.gradle.kts
│   └── settings.gradle.kts
├── build.gradle.kts
└── settings.gradle.kts
My desire is to be able to run build in the root composite project within the IDE (Intellij) and it mimic the behavior of a multiproject execution, where everything underneath that project executes the task in turn.
In Groovy, one can just use the spread operator on includedBuilds*.tasks* in the composite project to wire it up, but in the Kotlin DSL, we only have access to task, which is a single TaskReference and no way to get a collection of Tasks (TaskCollection or Collection of Tasks) or collection of TaskReferences.
So in the rootProject of the composite build.gradle.kts, I have:
tasks {
val clean by getting {
gradle.includedBuilds.forEach { this.dependsOn(it.task(":cleanAll")) }
}
val build by getting {
gradle.includedBuilds.forEach { this.dependsOn(it.task(":buildAll")) }
}
}
Then in one of the included builds build.gradle.kts files, I have tried wiring them two different ways (well many but these are the two approaches):
// Variation 1
tasks {
val buildAll : GradleBuild by creating {
this.dependsOn(tasks.getByPath(":build"))
}
val cleanAll : Delete by creating {
this.dependsOn(tasks.getByPath(":clean"))
}
}
// Variation 2
tasks {
val buildAll: GradleBuild by creating {
subprojects.forEach {
this.dependsOn(it.tasks.getByPath(":build"))
}
}
val cleanAll: Delete by creating {
subprojects.forEach {
this.dependsOn(it.tasks.getByPath(":clean"))
}
}
}
// Variation 2.b
tasks {
val buildAll: GradleBuild by creating {
this.dependsOn(subprojects.mapNotNull(it.tasks.getByPath(":build")))
}
val cleanAll: Delete by creating {
this.dependsOn(subprojects.mapNotNull(it.tasks.getByPath(":clean")))
}
}
// I even used different ways to try and get the tasks such as it.tasks["root:library:build"], it.tasks[":library:build"], and it.tasks["library:build"] since I know that the included builds are executed in an isolated fashion. None of these worked
// The result was when I used absolute paths, gradle spat back that these tasks didn't exist (I assumed because they were lifecycle tasks).
Basically, trying the variations above only ever built and cleaned the rootProjects of the included builds and never the subprojects. Is this a bug?
I do not want to have to resort to needing knowledge of the underlying structure of the included builds to wire this up. That would be unsustainable. What am I doing wrong?
I'm using the following code to achieve this. First, create a settings.gradle.kts in the root that programmatically searches for builds for include:
rootDir.walk().filter {
it != rootDir && !it.name.startsWith(".") && it.resolve("build.gradle.kts").isFile
}.forEach(::includeBuild)
Then create a build.gradle.kts file in the root that "forwards" all root task invocations of the form all<TASK> to <INCLUDED_BUILD>:<TASK>:
tasks.addRule("Pattern: all<TASK>") {
val taskName = this
val taskPrefix = "all"
if (startsWith(taskPrefix)) {
task(taskName) {
gradle.includedBuilds.forEach { build ->
val buildTaskName = taskName.removePrefix(taskPrefix).decapitalize()
dependsOn(build.task(":$buildTaskName"))
}
}
}
}
This way, running ./gradlew allAssemble on the root project will effectively execute the assemble task on all included builds.
Okay, not sure what was going on and why those other methods didn't work, but I found a method that works and does not require me to manufacture synthetic tasks that depend on the lifecycle tasks.
Composite rootproject build.gradle.kts tasks remain the same as stated in the original question:
tasks {
val clean by getting {
gradle.includedBuilds.forEach { this.dependsOn(it.task(":cleanAll")) }
}
val build by getting {
gradle.includedBuilds.forEach { this.dependsOn(it.task(":buildAll")) }
}
}
Tasks declaration in included build root projects' build.gradle.kts-es need to collect and depend on tasks in the following way:
tasks {
val buildAll: GradleBuild by creating {
dependsOn(getTasksByName("build", true))
}
val cleanAll: Delete by creating {
dependsOn(getTasksByName("clean", true))
}
}
This will recursively gather the tasks. Although my previous techniques of iterating through all of the subprojects should have also done the trick since subprojects contains all subprojects, for some reason it wasn't working. This is though! Hopefully this helps out other people.

TaskExecutionException for gradle jettyRun

I am following the book 'Gradle in Action' page 67 attempting to call:
$ gradle jettyRun
...
* What went wrong:
Execution failed for task ':jettyRun'.
> org/eclipse/jetty/http/HttpField
...
With the --stacktrace option I obtain the following message:
...
* Exception is:
org.gradle.api.tasks.TaskExecutionException:Execution failed for task ':jettyRun'
...
Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.http.HttpField
...
My project is todo-webapp as per the book page 67:
$ tree
├── build.gradle
├── src
│   └── main
│   ├── java
│   │   └── com
│   │   └── manning
│   │   └── gia
│   │   └── todo
│   │   ├── model
│   │   │   └── ToDoItem.java
│   │   ├── repository
│   │   │   ├── InMemoryToDoRepository.java
│   │   │   └── ToDoRepository.java
│   │   └── web
│   │   └── ToDoServlet.java
│   └── webapp
│   ├── css
│   │   ├── base.css
│   │   └── bg.png
│   ├── jsp
│   │   ├── index.jsp
│   │   └── todo-list.jsp
│   └── WEB-INF
│   └── web.xml
and my build file is as follows:
$ cat build.gradle
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'jetty'
repositories {
mavenCentral()
}
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5',
'javax.servlet.jsp:jsp-api:2.1'
runtime 'javax.servlet:jstl:1.1.2',
'taglibs:standard:1.1.2'
}
following various threads online I have been trying to add a dependency to my build file such as:
jettyRun {
dependencies {
providedRuntime 'org.eclipse.jetty:jetty-http:9.4.0.RC0'
}
}
or indeed add the jar directly /usr/share/java/jetty9-http with runtime files(...). None of these worked. I am not too sure what to try next. Any suggestion is greatly appreciated.
EDIT: As indicated by Vampire, all I had to do is remove apply plugin: 'jetty' and instead following the line apply plugin: 'war', add the line:
apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin'
Then use gradle appRun instead of gradle jettyRun. All this is coming from the page Getting started of the gretty plugin.
As you can see at https://docs.gradle.org/current/userguide/userguide_single.html#jetty_plugin, the Jetty plugin is deprecated. Maybe you should try the Gretty plugin as suggested in the docs. :-)

Gradle plugin packaging - Why is plugin unexpectedly applied?

So I have:
buildSrc/
├── build.gradle
└── src
├── main
│   ├── groovy
│   │   └── build
│   │   ├── ExamplePlugin.groovy
│   │   └── ExampleTask.groovy
│   └── resources
│   └── META-INF
│   └── gradle-plugins
│   └── build.ExamplePlugin.properties
└── test
└── groovy
└── build
├── ExamplePluginTest.groovy
└── ExampleTaskTest.groovy
Question:
It seems like build.ExamplePlugin.properties maps directly to the build.ExamplePlugin.groovy. Is this the case? Seems terribly inefficient to have only one property in the file. Does it have to be fully qualified, i.e. does the name have to exactly match the full qualification of the class?
Now in the example, I see:
project.pluginManager.apply 'build.ExamplePlugin'
...however if I have that in my test, I get an error to the effect that the simple task the plugin defines, is already defined.
Why bother with test examples that require 'apply' when that is inappropriate for packaging?

Resources