In our team we have lot of projects built with Gradle. Some parts in the Gradle files are all the same. For example, we use Java 11 in all our projects. So my idea was that I could split up my build.gradle files into a common part, that is then synced from a central repository into every Gradle project while the project specific parts remain in build.gradle.
build.gradle:
plugins {
id 'java'
//...
}
apply from: "common.gradle.kts"
dependencies {
// ...
}
common.gradle.kts
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
test {
useJUnitPlatform()
}
Now I get the error message by Gradle
* Where:
Script '/Users/.../common.gradle.kts' line: 4
* What went wrong:
Script compilation errors:
Line 04: java {
^ Expression 'java' cannot be invoked as a function. The function 'invoke()' is not found
Line 04: java {
^ Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val PluginDependenciesSpec.java: PluginDependencySpec defined in org.gradle.kotlin.dsl
Line 05: toolchain {
^ Unresolved reference: toolchain
Line 06: languageVersion = JavaLanguageVersion.of(11)
^ Unresolved reference: languageVersion
Line 09: test {
^ Unresolved reference: test
Line 10: useJUnitPlatform()
^ Unresolved reference: useJUnitPlatform
6 errors
For some configurations I found an alternative using a more generic API that works, though it is a lot of effort to find the corresponding alternatives and in the end no one can guarantee that they do exactly the same thing:
tasks.withType<JavaCompile> {
options.release.set(11)
}
So the question remains: why can't I use the DSL functions java or test in my externalized common.gradle.kts?
It seems it has to do something with the use of Kotlin script, at least if I use Groovy too for my externalized script, it works.
In your common.gradle.kts, java { } is generated helper Kotlin DSL function. Gradle doesn't know about the Kotlin DSL helpers unless
it's part of a build (not using apply(from = "...")
the java plugin is applied
Understanding when type-safe model accessors are available
Only the main project build scripts and precompiled project script plugins have type-safe model accessors. Initialization scripts, settings scripts, script plugins do not. These limitations will be removed in a future Gradle release.
Reacting to plugins
https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#reacting_to_plugins
It's still possible to have your common.gradle.kts - but it needs to configure the Java Plugin without the Kotlin DSLs
// common.gradle.kts
plugins.withType(JavaBasePlugin::class).configureEach {
// the project has the Java plugin
project.extensions.getByType<JavaPluginExtension>().apply {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
}
This is a little more clunky because the Kotlin DSL helpers aren't available.
buildSrc convention plugins
If you want to create conventions for a single project, then the standard way is to create buildSrc convention plugins.
https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources
This is best for projects that have lots of subprojects.
// $projectRoot/buildSrc/src/main/kotlin/java-convention.gradle.kts
plugins {
java
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
test {
useJUnitPlatform()
}
See the answer here for more detail: https://stackoverflow.com/a/71892685/4161471
Sharing plugins between projects
https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html
It's possible to share convention plugins between projects, so long as you have a Maven repo to deploy your plugins.
It's even possible to create your own Gradle distribution, so the plugins are included along with the Gradle wrapper! https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:custom_gradle_distribution
However I'd advise against these approaches. Generally the time invested in creating shared plugins will never be faster than just copy and pasting buildSrc convention plugins. And more importantly, it's best to keep projects independent. While sharing build conventions seems like a good idea, it introduces dependencies that make it hard to track problems, and makes updating the shared plugins hard as you're not sure what the consequences might be. This article explains more https://phauer.com/2016/dont-share-libraries-among-microservices/
Related
What I would like to achieve
I would like to have a manual gradle task that I can generate Java classes based on Json schema. However, I don't want this task to run when I run other fx. gradle build.
What I did
Firstly I've create simple gradle java project with
gradle init
Then I have added jsonschema2dataclass plugin and configure it as follows (my current build.gradle):
/*
* This file was generated by the Gradle 'init' task.
* (...)
*/
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
id "org.jsonschema2dataclass" version "4.5.0"
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
// This dependency is used by the application.
implementation 'com.google.guava:guava:29.0-jre'
}
application {
// Define the main class for the application.
mainClass = 'GradlePlayground.App'
}
jsonSchema2Pojo {
includeGeneratedAnnotation = true
generateBuilders = true
targetPackage = 'org.example.api' // specify package for your needs
targetDirectoryPrefix = file("${project.rootDir}/app/src/main/java")
source.setFrom files("${project.rootDir}/app/src/main/resources/json")
}
What I've tried
Add task.enabled = false
Put plugin configuration into another task
Check source code of plugin to find a way to disconnect this task from build task
But all above trails have failed. When I run gradle tasks I can always see generateJsonSchema2DataClass and generateJsonSchema2DataClass0 as part of build tasks.
I'm using java 8 and gradle 6.9.3
I'm the author of the gradle plugin.
Short answer for your question is "no, it's not an intended flow for a normal project". However, it's always possible to create a new project, build it once and extract the sources.
Could you please explain, what the reason you want to exclude run from the build chain?
My only guess is to build and publish models. And if this is a case, it's possible to do this using some gradle magic, which described in discussions in the GitHub project
UPD: Based on your gradle script, I have a lot of "why" questions I'd like to ask, it'll be easier if you
Please forgive me in advance as I've been using Java since the early 2000s and have been slow to transition new projects toward being compliant with Project Jigsaw and modules (introduced in Java 9.) I'm stuck and hoping someone can help me out. I've tried to create as minimal project as possible to help me focus on the problem. I'm using:
JavaFX - I followed the instructions on https://openjfx.io/openjfx-docs/ using their guidance for Modular Gradle with IntelliJ, though I'm not interested in building an image yet, so I'm leaving jlink out of it. This worked just fine.
Tablesaw for some pandas-like data crunching
JUnit 5.8.2
I have only one class file, HelloFX down the package org.hello.
Executing..
$ .\gradlew run
I get a ResolutionException error from Gradle while trying to run the project:
Error occurred during initialization of boot layer java.lang.module.ResolutionException: Modules shims and RoaringBitmap export package org.roaringbitmap to module listenablefuture
My project tree (all located in a root folder called TestProject):
./gradle
./gradlew
./build.gradle
./.gradle
./gradlew.bat
./settings.gradle
./.idea
./src
./src/test
./src/test/resources
./src/test/java
./src/main
./src/main/resources
./src/main/java
./src/main/java/module-info.java
./src/main/java/org
./src/main/java/org/hello
./src/main/java/org/hello/HelloTS.java
Here are the pertinent files:
settings.gradle
rootProject.name = 'TestProject'
build.gradle
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.12'
id 'org.javamodularity.moduleplugin' version '1.8.10'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
javafx {
version = "17.0.2"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
test {
useJUnitPlatform()
}
dependencies {
implementation 'tech.tablesaw:tablesaw-core:0.42.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}
application {
mainModule = "$moduleName"
mainClassName = "org.hello.HelloFX"
}
module-info.java
module TestProject {
requires javafx.graphics;
requires javafx.controls;
requires tablesaw.core;
exports org.hello;
}
What I've discovered so far:
Eliminate Tablesaw - Comment out requires tablesaw.core; from module-info.java and implementation 'tech.tablesaw:tablesaw-core:0.42.0' from build.gradle and my little JavaFX app works just fine with modules, but then I lose Tablesaw.
Eliminate modules - Remove module-info.java, then comment out the mainModule line in build.gradle. Then, I can run both a sample JavaFX program and a sample Tablesaw program by simply changing mainClassName to the program I want to run. I can even add some Tablesaw code in my sample JavaFX app, and it works. This is my backup plan, since it gives me what I want, albiet without modularization.
So, I'm really stumped here. This post didn't help, nor did any other that tried to address this weird ResolutionException error from Gradle. My guess is that Tablesaw is not module compliant? Or I need some sort of exclusion clause in my dependencies for Tablesaw? I tried to use the java-library plugin and use the api clause in build.gradle for Tablesaw as it seemed like that plugin is for non-modular libraries, but that didn't work.
There must be a way to do this, but admittedly I am about ready to throw in the towel and, yet again, just go back to non-modular development for my latest project. I have been a huge fan of Java since its inception, (even fully certified back in the Sun Microsystems days! That'll date me!) I understand why modularization has been introduced. Makes complete sense! But frankly, I'm finding its implementation to be quite challenging to embrace.
Any help would be greatly appreciated. Thank you kindly!
Tablesaw 0.42.0 isn’t built to support the Java module system.
It has no module-info.
It uses shading for its dependencies
It uses dependencies like RoaringBitmap that have issues if you try to use them with the module system.
I suggest you log an issue with Tablesaw requesting that they modularize the library.
In the meantime, JavaFX should be run from the module path as it is only supported that way, but it will probably be better to run Tablesaw from the class path.
You can put JavaFX on the module path and add the JavaFX modules via command line switches.
Put Tablesaw on the class path, don’t add it as a module.
Don’t define a module-info for your app, create a non-modular app that adds the JavaFX modules via switches. This means that your app code is also on the class path so it can access Tablesaw and it can also access JavaFX modules through virtue of the command line switches.
I don’t use Gradle, so I can’t provide you the exact build script you need for this.
For more info see:
openjfx.Io getting started documentation on non-modular with gradle for your IDE
You will probably be able to package your app using the:
badass runtime plugin.
Currently I'm working with an old Kotlin DSL build script that publishes an Android AAR library to a Maven repository. The dependencies are added to the pom-default.xml via simplistic iteration through the implementation configurations. Similar to this:
withXml {
val dependenciesNode = asNode().appendNode("dependencies")
configurations.getByName("implementation") {
dependencies.forEach {
val dependencyNode = dependenciesNode.appendNode("dependency")
dependencyNode.appendNode("groupId", it.group)
dependencyNode.appendNode("artifactId", it.name)
dependencyNode.appendNode("version", it.version)
}
}
}
This doesn't translate the exclusion statements, and so now publishes useless broken libraries with conflicting classes. I'd like to be able to use the Android Gradle Plugin's generated build artifact components instead, because I understand that by using these, the pom file will be automatically generated with the right list of dependencies (presumably with the exclusion clauses).
The documentation to do this is given here:
https://developer.android.com/studio/build/maven-publish-plugin
Unfortunately, as always, the example given is Groovy, and I cannot find any example of the syntax you would use in a Kotlin Gradle script.
from(components["release"])
...didn't work.
Okay, turns out...
from(components["release"])
...does work, I just didn't put in the...
afterEvaluate {
...clause, recklessly assuming this was just a Groovy thing.
Remember kids, stay away from drugs, work hard at school and follow the documentation faithfully, to a tee.
I would like to be a able to get Eclipse to ignore one Gradle project, and instead use a pre-built version of it.
Background
I have a project "parser" written in Scala, and a dozen others written in Java. The weakest link in my tool-set is Scala IDE. I use this plugin to edit & compile Scala code, but unfortunately it breaks the Java (JDT) tooling quite badly in mixed-language projects*.
Specifically: Call-hierarchy is missing results, searches crash and so on. Also Scala IDE appears to have lost funding and the issues sound fairly fundamental, so I'm not holding my breath for these issues to be fixed.
With Maven (m2e) I had a workaround I was quite happy with:
Build as a .jar put into my local .m2 repository:
cd parser; mvn install
In Eclipse, close the "parser" project
"Like magic", m2e simply picked up the most recent 'installed' .jar and used it in place of the closed project.
An awesome answer would be how to get Gradle to do that!
However all I wish for is any solution that meets these...
Requirements
That I can open Project parser when necessary (which is seldom),
to edit and build changes via the Gradle command-line.
I will close it when done.
Other projects use the built .jar from my local .m2 repo.
(It's fine if they always do so.)
The change must not affect others who don't use Eclipse
(ideally) the change can be used by other Eclipse users
Approaches
A similar question had this good answer by #lance-java with a number of general suggestions. I think I can rule out these ideas:
composite build support / multiple repos. Other team members wouldn't think it makes sense to build this project separately, as it is quite closely integrated with the others.
dependency substitution rules - doesn't appear to meet requirement 3.
Something along the lines of lance-java's idea #4 sounds viable. Paraphrasing...
"use the eclipse plugin [in conjunction with] Buildship, e.g. using the whenMerged hook to tweak the generated .classpath [of all the Java projects]."
UPDATE: [18 Apr]: I had hit a brick wall in this approach. Buildship was not putting the built .jar onto the runtime classpath. (UPDATE 2: Now resolved - see my answer.)
Questions
The main question: How can I structure a solution to this, that will actually work & avoid any major pitfalls?
Note that the project itself has a few dependencies, specifically:
dependencies {
compile 'org.scala-lang:scala-library:2.12.4'
compileOnly 'com.google.code.findbugs:jsr305:1.3.9'
antlr 'org.antlr:antlr4:4.5.3'
}
So a sub-question may be: How to pull these in into the other projects without duplicating the definition? (If that doesn't work automatically.)
So the solution was a bit involved. After adding 'maven-publish' to create the library, I then implemented the following to force Eclipse to use the prebuilt library:
subprojects {
// Additional configuration to manipulate the Eclipse classpaths
configurations {
parserSubstitution
}
dependencies {
parserSubstitution module("com.example:parser:${project.version}")
}
apply plugin: 'eclipse'
eclipse {
classpath {
plusConfigurations += [ configurations.pseLangSubstitution ]
file {
whenMerged { cp ->
// Get Gradle to add the depedency upon
// parser-xxx.jar via 'plusConfigurations' above.
// Then this here if we have a dependency on Project(':parser')
// - If so, remove it (completing the project -> jar substitution).
// - If not, remove the .jar dependency: it wasn't needed.
def usesParser = entries.removeAll {
it instanceof ProjectDependency && it.path.startsWith('/parser')
}
def parserJar =
cp.entries.find { it instanceof Library && it.path.contains('parser-') }
if (usesParser) {
// This trick stops Buildship deleting it from the runtime classpath
parserJar ?. entryAttributes ?. remove("gradle_used_by_scope")
} else {
cp.entries.remove { parserJar }
}
}
}
}
So there are 2 parts to this:
Using 'plusConfigurations' felt a bit round-about. I ended up doing this because I could not see how to construct class Library classpath entries directly. However it could well be that this is required to implement the 'transient dependencies' correctly anyway. (See the end of the question.)
The trick to stop Buildship removing the .jar from the runtime classpath (thus deviating from a Gradle command-line launch) was provided to me by a Gradle developer in this discussion.
Usage
The solution works just as I hoped. Every time some code in this library is modified, I execute the following task of mine on the command line (which also does some other code & resource generation steps, in addition to building the parser jar):
./gradlew generateEclipse
Then in Eclipse I press keyboard shortcuts for "Gradle -> Refresh Gradle Projects", Build.
And harmony is restored. :-)
Navigating to the (prebuilt) source of parser works.
If I need to edit the source, I can open the parser project and edit it. Scala-IDE still does a good job for this.
When I'm done I execute the command, close the project and my Java tools are happy.
In parser project
You shoud use the maven-publish plugin with the publishToMavenLocal task
apply plugin: 'maven-publish'
group = 'your.company'
version = '1.0.0'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
pom.withXml {
def root = asNode()
root.appendNode('name', 'Your parser project name')
root.appendNode('description', 'Your parser project description')
}
}
}
}
Everytime you make a modification, just change the version number if necessary and go with gradle publishToMavenLocal
In other java project using parser
Just use parser as a regular dependency :
repositories {
mavenLocal()
...
}
compile 'your.company:parser:1.0.0'
If my understanding of your situation is good, it should do the trick.
The Android Gradle plugin adds support for build types and build variants, which let you select which version of your application you want to build at the build step (ex, debug or release).
This is a very useful feature for Gradle projects, as you can have 2 versions of your application that may behave differently in some situations, or have different configuration files or properties depending on the type of build.
Now, my question is: is there a similar feature/implementation for non-Android Java projects from Maven or Gradle? I am looking specifically for Java web apps, but I presume the question may have a larger target as well.
Cutting it to the chase without vague answers, here is the official response from Luke Daley (Gradleware Engineer) on this matter:
It is something we are actively working on. We are working to support
the notion of variants in a general way, so that there will be a
consistent approach across domains. It's a deep, deep, change though
so there is a lot involved.
You can expect to see aspects of this rolling out in Gradle 2.5 and
on.
Later Edit: I've finally been able to get this working on a JavaEE webapp project by using SourceSets instead of Build Types & Variants. Considering that SourceSets have been around for a very very long time, apparently you could've done this ages ago... But not even gradle engineers were able to properly explain how to do so...
Anyhow, check out the build.gradle code below, where we use the same output directory for both SourceSets, then specify the location for the WAR plugin to build from:
apply plugin: 'war'
sourceSets {
main {
output.resourcesDir = 'build/resources'
output.classesDir = 'build/classes'
}
debug {
java {
compileClasspath += main.output
runtimeClasspath += main.output
}
output.resourcesDir = 'build/resources'
output.classesDir = 'build/classes'
}
}
task assembleDebugWar(type: War) {
from sourceSets.debug.output
archiveName "ROOT.war"
}
task assembleReleaseWar(type: War) {
from sourceSets.main.output
archiveName "ROOT.war"
}