Multimodule Gradle projects - spring

I'm currently building a client-server app with multiple distributed apis (via Java Spring Boot), and I've abstracted the API components (#Controller, #Service #Mapper, and #Repository).
I have a multi-module gradle project that looks something like this:
---- Projects
------ properties.gradle
------ settings.gradle
------ ClientProj
-------- ClientApplication
-------- build.gradle
------ ApiProj1
-------- ApiProj1Application
-------- build.gradle
------ ApiProj2
-------- ApiProj2Application
-------- build.gradle
------ common
-------- build.gradle
------ api-components
-------- build.gradle
But I want to possibly do something like this instead:
(Notice that I'm trying to wrap all the api related components together).
---- Projects
------ properties.gradle
------ settings.gradle
------ ClientProj
-------- ClientApplication
-------- build.gradle
------ common
-------- build.gradle
------ api
-------- ApiProj1
---------- ApiProj1Application
---------- build.gradle
-------- ApiProj2
---------- ApiProj2Application
---------- build.gradle
------- api-components
---------- build.gradle
From what I've seen online, I haven't noticed multimodes within other modules like the last example I show with the api. I'd like to ask everyone whether this design is acceptable, and if so, whether these nested api modules would be considered subprojects. From my unsuccessful attempts thus far, it seems really difficult to access nested projects when constructed this way.
I'm a bit more than beginner for using Gradle but multimodules and subprojects are new to me and I think has a lot of potential to isolate concerns and organize code. Any feedback would be greatly appreciated! Thank you in advance. :)

It's still possible to use the second structure. Your first structure's settings.gradle.kts must be like:
include(
"ClientProj",
"common",
"ApiProj1",
"ApiProj2",
"api-components",
)
For the second, it should be changed to:
include(
"ClientProj",
"common",
"ApiProj1",
"ApiProj2",
"api-components",
)
project(":ApiProj1").projectDir = file("api/ApiProj1")
project(":ApiProj2").projectDir = file("api/ApiProj2")
project(":api-components").projectDir = file("api/api-components")

Related

Gradle tasks definition

These three forms to define a task in build.gradle seem identical. All of them call org.gradle.api.internal.project.DefaultProject#task(java.lang.String, groovy.lang.Closure), but really I can't understand how the second and the third one can work.
def myAction = {t -> println "${t.name} [${t.class.name}]"}
task('myTaskA') {task ->
group = 'MyTasks'
description = name
doLast myAction
}
task myTaskB {task ->
group = 'MyTasks'
description = name
doLast myAction
}
task myTaskC() {task ->
group = 'MyTasks'
description = name
doLast myAction
}
Task Configuration Avoidance: As of Gradle 5.1, it is recommend that the configuration avoidance APIs are used whenever tasks are created by custom plugins. The Configuration Avoidance API will co-exists with the existing APIs that will be replaced with the usual deprecation process over several major releases. In a nutshell, the API allows builds to avoid the cost of creating and configuring tasks during Gradle’s configuration phase when those tasks will never be executed, which can have a significant impact on total configuration time.
Instead of: “task ... {…}”, “task ... << {…}”, “task('...') {…}”, etc. use “tasks.register('...') {…}” API.

Gradle artifacts declaration

I need some guidance and clarification about how artifacts are declared in gradle tasks, I would like to upload files to maven/artifactory reading these files from any kind of output container instead of having to hardcode the path for each artifact generated.
This is simpler if you have a JAR, ZIP, File, and a few other types, because you can make use of project.artifacts.add(...), but this is not trivial when you have some random file types.
Im going to provide an example to specify and clarify what I need exactly:
if I have:
task generateMyFile {
doLast {
buildDir.mkdirs()
['touch', new File(buildDir, 'myfile.random').absolutePath].execute().waitFor()
}
}
What's the right way to declare myfile.random as the output for generateMyFile ?
How do I declare myfile.random to be a valid artifact that will be used by maven/artifactory later by the 'publish' task ? currently I assume the file was generated in build/myfile.random, but Im looking for a smarter solution, and gradle documentation (or any other source) is not clear about this.
Thanks in advance

How to assign default values for ext based properties in gradle

I am working on a gradle plugin with a task where it accesses the extra params using the project object itself like
project.extraParam1
project.extraParam2
Now I can use this plugin from another project and pass the parameters in the build.gradle file as
ext {
extraParam1 value1
extraParam2 value2
}
I mean I apply this plugin in another project's build.gradle. Define the ext parameters and call the plugin task and it works. The task is able to access the extra properties. However, I want to set some default values to these, so that even though the project which is using the plugin doesn't define the ext parameter, it has some default values and works for default values.
In your plugin, you can do something like that :
def extraParam1 = project.hasProperty('extraParam1') ? project.extraParam1 : 'default value'

Can a build template be based on another build template on TeamCity?

I am using TeamCity 9.0.2, and I would like to make a template implement another template, or make a build configuration implement more than one template.
Can this be achieved?
This was not available when you asked the question but since Team City 10 you can now use Kotlin to configure your builds and thus your templates.
From this you can make Templates implement other Templates.
I myself have made Templates inherit from other templates to cut down on reconfiguration time and to not have to repeat myself so many times.
open class TheBaseTemplate(uuidIn: String, extIdIn: String, nameIn: String, additionalSettings: Template.() -> Unit) : Template({
uuid = uuidIn
extId = extIdIn
name = nameIn
/* all the other settings that are the same for the derived templates*/
additionalSettings()
})
object DerivedTemplateA : TheBaseTemplate("myUuidA", "myExtIdA", "myNameA", {
params {
param("set this", "to this")
}
})
object DerivedTemplateB : TheBaseTemplate("myUuidB", "myExtIdB", "myNameB", {
params {
param("set this", "to that")
}
})
object Project : Project({
uuid = "project uuid"
extId = "project extid"
name = "project name"
buildType {
template(DerivedTemplateA)
/* the uuid, extId and name are set here */
}
buildType {
template(DerivedTemplateB)
/* the uuid, extId and name are set here */
}
template(DerivedTemplateA)
template(DerivedTemplateB)
})
The above code might be very hard to understand. It will take some time to familiarise yourself with Kotlin, what it does, and how it interfaces with TeamCity. I should point out that some imports are missing.
Additionally, take the example with a pinch of salt. It is a quick example to demonstrate one way of templates implementing other templates. Do not take this example as the definitive way to do things.
Unfortunately, this is currently not possible but already requested for a long time in TW-12153 (maybe you would like to vote for it).
To share several build steps among several build configurations or build configuration templates, I am using meta runners:
A Meta-Runner allows you to extract build steps, requirements and parameters from a build configuration and create a build runner out of them.
This build runner can then be used as any other build runner in a build step of any other build configuration or template.
Although using meta runners works as a workaround for us, editing meta runners is not as convenient as editing a build configuration template (as it usually requires editing the meta runner definition XML file by hand).
Update 2021
As #zosal points out in his answer TeamCity meanwhile provides another way of sharing common build configuration data or logic by means of the Kotlin DSL. The Kotlin DSL is a very powerful tool but may not always fit in your specific scenario. I would recommend to at least give it a try or watch one of the introductory tutorial videos.

gradle: how do I list tasks introduced by a certain plugin

Probably a simple question but I can't find a way to list which tasks are introduced by the plugins that get applied in a build.gradle file.
So, say that your build.gradle is simply:
apply plugin: 'java'
is there a simple way to make gradle list all the tasks introduced by that plugin?
PS: that would come handy in case of messy and large build files with dozens of applied plugins
PS2: I'm not asking about the dependencies of the tasks. My question is different and quite clear. Each plugin that I apply introduces some tasks of its own (never mind what depends on what). The question is which are the newly introduced tasks in the first place?
I'm afraid it is not possible because of the nature how gradle plugins are applied.
If you take a look at Plugin interface, you will see it has a single apply(Project p) method. Plugin responsibility is to configure a project - it can add specific tasks / configurations / etc. For example, gradle JavaPlugin is stateless, so you can't get tasks from it.
The only solution that comes to mind is to get a difference of tasks after the plugin is applied:
build.gradle
def tasksBefore = [], tasksAfter = []
project.tasks.each { tasksBefore.add(it.name) } // get all tasks
apply(plugin: 'idea') // apply plugin
project.tasks.each { tasksAfter.add(it.name) } // get all tasks
tasksAfter.removeAll(tasksBefore); // get the difference
println 'idea tasks: ' + tasksAfter;
This will print tasks that were added by Idea plugin:
idea tasks: [cleanIdea, cleanIdeaModule, cleanIdeaProject,
cleanIdeaWorkspace, idea, ideaModule, ideaProject, ideaWorkspace]
You can play a bit with this code and build an acceptable solution.
In some cases origin from specific plugin can be restored by checking the task's group and name:
tasks.findAll { it.group == 'verification' && it.name.startsWith('jacoco') }.each { task ->
println(task.name)
}

Resources