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

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.

Related

spark-submit error loading class with fatjar on macOS

I am trying to run a simple hello world spark application
This is my code
package com.sd.proj.executables
import org.apache.spark.sql.functions.lit
import org.apache.spark.sql.{DataFrame, SparkSession}
class SparkConn {
def getSparkConn(caller:String) : SparkSession = {
val conf = new SparkConf().setAppName(caller)
val spark: SparkSession = SparkSession.builder.config(conf).getOrCreate()
spark
}
}
object HelloSpark {
def sparkDF()(implicit spark:SparkSession):DataFrame = {
spark.emptyDataFrame
.withColumn("Title",lit("Hello World!!!"))
}
def main(args:Array[String]):Unit ={
val sparkConn = new SparkConn()
implicit val spark = sparkConn.getSparkConn(this.getClass.getName)
val df = sparkDF()
df.show(false)
spark.stop()
}
}
This is my build.gradle
plugins {
id 'scala'
id 'idea'
id 'org.scoverage' version '7.0.0'
}
repositories {
mavenCentral()
}
sourceSets{
main{
scala.srcDirs = ['src/main/scala']
resources.srcDirs = ['src/main/resources']
}
test{
scala.srcDirs = ['src/test/scala']
resources.srcDirs = ['src/test/resources']
}
}
dependencies {
//scala
implementation 'org.scala-lang:scala-library:2.12.15'
implementation 'org.scala-lang:scala-reflect:2.12.15'
implementation 'org.scala-lang:scala-compiler:2.12.15'
//spark
implementation 'org.apache.spark:spark-core_2.12:3.2.0'
implementation 'org.apache.spark:spark-sql_2.12:3.2.0'
//junit
testImplementation 'junit:junit:4.12'
testImplementation 'org.scalatestplus:scalatestplus-junit_2.12:1.0.0-M2'
}
scoverage{
scoverageVersion = "1.4.11"
minimumRate=0.01
}
task fatJar(type:Jar){
zip64 true
manifest {
attributes 'Implementation-Title': 'Gradle Fat Jar',
'Implementation-Version': '0.1',
'Main-Class': 'com.sd.proj.executables.HelloSpark'
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
baseName = project.name + '-fat'
from {
configurations.runtimeClasspath.collect {
it.isDirectory() ? it : zipTree(it)
}
}
with jar
}
and this is the project structure
.
├── README.md
├── build
│   ├── classes
│   │   └── scala
│   │   └── main
│   │   └── com
│   │   └── sd
│   │   └── proj
│   │   └── executables
│   │   ├── HelloSpark$.class
│   │   └── HelloSpark.class
│   ├── generated
│   │   └── sources
│   │   └── annotationProcessor
│   │   └── scala
│   │   └── main
│   ├── libs
│   │   ├── HelloSpark-fat.jar
│   │   └── HelloSpark.jar
│   └── tmp
│   ├── compileScala
│   ├── fatJar
│   │   └── MANIFEST.MF
│   ├── jar
│   │   └── MANIFEST.MF
│   └── scala
│   ├── classfileBackup
│   └── compilerAnalysis
│   ├── compileScala.analysis
│   └── compileScala.mapping
├── build.gradle
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
├── spark_submit.sh
└── src
├── main
│   ├── resources
│   └── scala
│   └── com
│   └── sd
│   └── proj
│   └── executables
│   └── HelloSpark.scala
└── test
├── resources
└── scala
my spark-submit script is
#!/bin/bash
echo "Running spark-submit..."
SPARK_HOME=/opt/homebrew/Cellar/apache-spark/3.2.1
export PATH="$SPARK_HOME/bin/:$PATH"
JARFILE=`pwd`/build/libs/HelloSpark-fat.jar
# Run it locally
echo "cmd : ${SPARK_HOME}/bin/spark-submit --class \"com.sd.proj.executables.HelloSpark\" --master local $JARFILE"
${SPARK_HOME}/bin/spark-submit --class "com.sd.proj.executables.HelloSpark" --master local $JARFILE
both scala and spark are installed on my mac
% type spark-submit
spark-submit is /opt/homebrew/bin/spark-submit
% type scala
scala is /opt/homebrew/opt/scala#2.12/bin/scala
When I run above spark-submit it fails saying
**Error: Failed to load class com.sd.proj.executables.HelloSpark.
**
% bash spark_submit.sh
Running spark-submit...
cmd : /opt/homebrew/Cellar/apache-spark/3.2.1/bin/spark-submit --class "com.sd.proj.executables.HelloSpark" --master local /Users/dsam05/IdeaProjects/HelloSpark/build/libs/HelloSpark-fat.jar
22/11/12 14:35:14 WARN Utils: Your hostname, Soumyajits-MacBook-Air.local resolves to a loopback address: 127.0.0.1; using 192.168.2.21 instead (on interface en0)
22/11/12 14:35:14 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.apache.spark.unsafe.Platform (file:/opt/homebrew/Cellar/apache-spark/3.2.1/libexec/jars/spark-unsafe_2.12-3.2.1.jar) to constructor java.nio.DirectByteBuffer(long,int)
WARNING: Please consider reporting this to the maintainers of org.apache.spark.unsafe.Platform
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Error: Failed to load class com.sd.proj.executables.HelloSpark.
log4j:WARN No appenders could be found for logger (org.apache.spark.util.ShutdownHookManager).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
I have never run spark on a mac before, can someone please guilde what am I doing incorrectly here
this is on a M1 mac, macOS 13
Solved this problem, posting as it might help someone else
Removed task fatJar from my build.gradle
then added this config
plugins {
//added this entry on top of what is already present
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
//...
//same config as in question above
//...
shadowJar{
zip64 true
}
now the jar is created as HelloSpark-all.jar
but runs perfectly
22/11/19 23:25:12 INFO CodeGenerator: Code generated in 88.62025 ms
22/11/19 23:25:12 INFO CodeGenerator: Code generated in 8.248958 ms
+--------------+
|Title |
+--------------+
|Hello World!!!|
+--------------+
22/11/19 23:25:12 INFO SparkUI: Stopped Spark web UI at http://localhost:4040

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)

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

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.

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