Creating a Gradle Custom Plugin with Java - java

I'm creating a build process with Gradle and I want to provide a plugin that uses Java code. The Gradle plugin documentation says this is possible:
You can implement a custom plugin in any language you like, provided the implementation ends up compiled as bytecode. For the examples here, we are going to use Groovy as the implementation language. You could use Java or Scala instead, if you want.
However, after multiple hours of Googling and reading, I have yet to find any explanation of how to create a Gradle custom plugin with Java. It seems like you could create the code for it in a directory like:
<rootProjectDir>/buildSrc/src/main/java/
MyGradlePlugin.java
MyGradleTasks.java
But the question then becomes:
How to implement the plugin class and tasks in Java to be compatible with Gradle?
How to get Gradle to recognize the Java classes and tasks so you can use them in a build?
Can you just reference the plugin class like you do for the Groovy equivalent?
src/main/resources/META-INF/gradle-plugins/myjavaplugin.properties
implementation-class=org.me.MyGradlePlugin
I realize that I could just call the Java code with project.javaexec or JavaExec, but I'm concerned this will make my build process less portable.

Here's a basic, stand-alone, Java-based Gradle plugin and the steps to get it working:
Make sure Gradle 1.6 or higher and Java JDK is installed
Create these files below with the directory structure indicated
Change directories to the <projectRoot>/plugin directory
Execute the plugin build: $ gradle uploadArchives This (very important) step compiles the Java code and puts it in your local Maven repo (../repo).
Now execute the consumer script by changing directories to <projectRoot>/consumer
Execute the script that depends on the plugin: $ gradle checkitout
Java Classes
projectRoot/plugin/src/main/java/org/joefernandez/gradle/MyJavaPlugin.java
package org.joefernandez.gradle;
import org.gradle.api.Project;
import org.gradle.api.Plugin;
public class MyJavaPlugin implements Plugin<Project> {
#Override
public void apply(Project target) {
target.task("javaTask");
}
}
projectRoot/plugin/src/main/java/org/joefernandez/gradle/MyJavaTask.java
package org.joefernandez.gradle;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
public class MyJavaTask extends DefaultTask {
#TaskAction
public void javaTask() {
System.out.println("Hello from MyJavaTask");
}
}
Plugin Class declaration
projectRoot/plugin/src/main/resources/META-INF/gradle-plugins/test-plugin.properties
implementation-class=org.joefernandez.gradle.MyJavaPlugin
Plugin Build Script
Note the uploadArchives task: You must run this task to make the plugin available to the consumer script.
projectRoot/plugin/build.gradle
apply plugin: 'java'
dependencies {
compile gradleApi()
}
apply plugin: 'maven'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.11'
}
group = 'org.joefernandez'
version = '1.0-SNAPSHOT'
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
Settings for the Plugin
projectRoot/plugin/settings.gradle
rootProject.name = 'MyJavaPlugin'
Root script
projectRoot/build.gradle
apply plugin: 'java'
dependencies {
compile gradleApi()
}
Consumer script
projectRoot/consumer/build.gradle
buildscript {
repositories {
maven {
url uri('../repo')
}
}
dependencies {
classpath group: 'org.joefernandez',
name: 'MyJavaPlugin',
version: '1.0-SNAPSHOT'
}
}
apply plugin: 'test-plugin'
task checkitout(type: org.joefernandez.gradle.MyJavaTask) {
println("running consumer task!")
}

I would like to propose a few amendments to Joe's great answer.
projectRoot/plugin/src/main/java/org/joefernandez/gradle/MyJavaPlugin.java
This change wires the task into the supplied project without the need to sub-class it.
package org.joefernandez.gradle;
import org.gradle.api.Project;
import org.gradle.api.Plugin;
public class MyJavaPlugin implements Plugin<Project> {
#Override
public void apply(Project target) {
// CHANGE HERE
target.getTasks().create("javaTask", MyJavaTask.class);
}
}
Consumer script
projectRoot/consumer/build.gradle
This change removes the now unnecessary task definition
buildscript {
repositories {
maven {
url uri('../repo')
}
}
dependencies {
classpath group: 'org.joefernandez',
name: 'MyJavaPlugin',
version: '1.0-SNAPSHOT'
}
}
apply plugin: 'test-plugin'
// CHANGE HERE
This them changes step 6, to:
Execute the script that depends on the plugin: $ gradle javaTask

Plugin classes must implement the org.gradle.api.Plugin interface. Task classes typically extend the org.gradle.api.DefaultTask class. It doesn't matter which (JVM) language is used for this. The Gradle codebase contains many plugin and task classes implemented in Java.
How to build and ship a plugin is another question, again mostly unrelated to which language is used. See the Gradle User Guide and samples/customPlugin in the full Gradle distribution.

Related

How to simulate a gradle build script in junit test?

I am playing a little bit with my own gradle plugin (for gradle 6.5.1). Now I wrote a small test (implemented in java) which is not working:
Project project = ProjectBuilder.builder().build();
ScriptHandler buildscript = project.getBuildscript();
Action<? super MavenArtifactRepository> action = new Action<MavenArtifactRepository>() {
#Override
public void execute(MavenArtifactRepository mavenArtifactRepository) {
mavenArtifactRepository.setUrl("https://plugins.gradle.org/m2/");
}
};
buildscript.getRepositories().maven(action);
buildscript.getDependencies().add("classpath", "io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE");
project.getPlugins().apply("java");
project.getPlugins().apply("io.spring.dependency-management");
It says Plugin with id 'io.spring.dependency-management' not found. I thought that I copied the original from https://plugins.gradle.org/plugin/io.spring.dependency-management
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE"
}
}
apply plugin: "io.spring.dependency-management"
But I guess thats not the case. :-) Any idea how to translate that snippet to java world?
If you want to apply an other plugin in your test you need the other plugin on your classpath. So you have to add to the build file of your gradle-plugin and than its there.
In this case it means write the following to your build.gradle
testImplementation "io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE"

Include scripts with Gradle Kotlin DSL

I'm trying to start using Kotlin DSL with gradle in the project with the following restrictions:
Project has different modules (moreover: sometimes these modules use different plugins, however if two projects uses the same plugin then version of the plugins are the same).
Project has internal corporate repositories only (e.g. we don't use jcenter directly, we use proxy for it).
What we have with Groovy:
Some common configurations items are excluded to the separate scripts. Please check the example below.
Gradle modules include these files.
As a result (just based on my example):
We don't need to add the same code lines into the each module.
The most of projects have difference just with dependency list.
I tried to reproduce the same with Gralde KTS and received the following difficulties:
I'm unable to apply plugin in the include file and use it in the module. In this case I receive compilation error (because plugin types are not added into the module script).
I'm unable to extract constants to the something common to use them in the each scripts (root build.gradle.kts inclusive). With Groovy I can just use variable like springBootVersion, however with Kotlin Script I have to create the same property in the each module file.
Precompiled script plugins does not work without public repositories access (e.g. I'm unable to configure common script file with idea "just use default embedded Kotlin Script version, download all dependencies from these urls: ...".
Include file sample:
apply plugin: 'kotlin'
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
Gradle module sample:
apply from: "${rootDir}/gradle/include/kotlin-common-include.gradle"
dependencies {
compile project(':my.project.libraries.common')
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion
}
Questions:
How can I put all common constants (such as dependency versions) to the separate file to include them just by using something like springBootVersion or Constants.springBootVersion with compile-time checks?
How can I extract plugin applying to the include scripts (to avoid Gradle module scripts overload)?
How can I use precompiled script plugins without public global repositories access?
Additional links:
Issue for Gradle KTS for plugin applying.
Issue for Gradle KTS for shared constants extracting.
There are limitations in Kotlin DSL currently (5.3) that prevents to have compile-time checks everywhere. In order for Kotlin DSL to work it has to add extensions on top of the Gradle API and it can't do it magically. First of all you need to go through Kotlin DSL Primer page to understand it.
How can I extract plugin applying to the include scripts (to avoid Gradle module scripts overload)?
The one way to do it is to use precompiled build scripts with Kotlin DSL Plugin. In order to do it you need to move your script into $rootDir/buildSrc project. Here how it might look like:
// $rootDir/buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
maven {
url = uri("http://host:8082/artifactory/...")
}
}
Then put your common script like that:
// $rootDir/buildSrc/src/main/kotlin/common.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.3.21"
}
tasks.compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
tasks.compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Then you can apply this script as to a plugin like that:
// $rootDir/build.gradle.kts
subprojects {
apply(id = "common")
}
How can I use precompiled script plugins without public global repositories access?
Configuring custom repositories for pre-compiled scripts plugin is no different that your usual build script. Do it like that:
// $rootDir/buildSrc/settings.gradle.kts
pluginManagement {
repositories.maven {
url = uri("http://host:8082/artifactory/...")
}
}
The other way around that if you don't want to use precompiled plugins is to configure extensions explicitly. You can do it like that:
// $rootDir/gradle/common.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
And in main script:
// $rootDir/build.gradle.kts
subprojects {
apply {
plugin(KotlinPlatformJvmPlugin::class)
from("common.gradle.kts")
}
}
How can I put all common constants (such as dependency versions) to the separate file to include them just by using something like springBootVersion or Constants.springBootVersion with compile-time checks?
There is no good way to do it currently. You can use extra properties, but it won't guarantee compile time checks. Something like that:
// $rootDir/dependencies.gradle.kts
// this will try to take configuration from existing ones
val compile by configurations
val api by configurations
dependencies {
compile("commons-io:commons-io:1.2.3")
api("some.dep")
}
// This will put your version into extra extension
extra["springBootVersion"] = "1.2.3"
And you can use it like this:
// $rootDir/build.gradle.kts
subprojects {
apply {
plugin<JavaLibraryPlugin>()
from("$rootDir/dependencies.gradle.kts")
}
And in your module:
// $rootDir/module/build.gradle.kts
// This will take existing dependency from extra
val springBootVersion: String by extra
dependencies {
compile("org.spring:boot:$springBootVersion")
}

Gradle custom plugin jar with dependencies

I'm trying to build a jar for a custom gradle plugin to be used by other gradle projects. I'm using java to write the plugin. I'm having a problem including dependencies in my jar. If I build the jar using the below build.gradle
plugins {
id 'groovy'
}
repositories{
mavenCentral()
}
dependencies {
compile gradleApi()
compile localGroovy()
compile 'com.google.guava:guava:27.0-jre'
testCompile 'junit:junit:4.12'
//compile 'org.apache.commons:commons-lang3:3.8.1'
}
group = 'com.mine'
version = '1.0'
I get a NoClassDefFound exception for guava classes when applying the plugin on a project. If I include a task to create a jar with dependencies like below in the build.gradle
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it)}
}
}
It says Plugin with Id 'my-plugin' not found. How do I include dependencies in a gradle plugin jar?
Your plugin project should be configured as a standalone Plugin project and then published to a maven repository, which will make dependencies resolution work; there is good documentation about writing custom plugin here, specially the following part : using Gradle plugin development plugin
There is also a good example of writing/publishing/consuming a custom Plugin in the Gradle examples here : https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/plugins (see the two subprojects publishing and consuming )
And here is a working example with a plugin that has dependency on external library (commons-lang for example):
Plugin project
build.gradle
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'maven-publish'
}
group 'org.gradle.sample.plugin'
version '0.1'
// pugin metadata configuration
gradlePlugin {
plugins {
myplugin {
id = "org.gradle.sample.plugin.myplugin"
implementationClass = "org.gradle.sample.plugin.MyPlugin"
}
}
}
// publish to local maven repo for testing
publishing {
repositories {
maven {
url "../repos/maven-repo"
}
}
}
// repo for dependences resolution
repositories{
jcenter()
}
// dependencies of this plugin
dependencies {
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
}
Plugin implementation : src/main/groovy/org/gradle/sample/plugin/MyPLugin.groovy
package org.gradle.sample.plugin
import org.apache.commons.lang3.StringUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin implements Plugin<Project> {
#Override
void apply(final Project project) {
println "Applying custom plugin... "
project.tasks.create('testPlugin'){
doLast{
println " custom plugin task executing."
println "Result: " + StringUtils.capitalize("stringtotest")
}
}
}
}
Build and publish this plugin ./gradlew publish : the plugin jar and "plugin marker artefacts" will be published to local maven repo in ../repos/maven-repo
Consumer project
build.gradle
plugins {
id 'java'
// import/apply your custom plugin
id 'org.gradle.sample.plugin.myplugin' version '0.1'
}
group 'org.gradle.sample.plugin'
version '0.1'
repositories{
maven {
url "../repos/maven-repo"
}
jcenter()
}
To test the plugin, try to execute the plugin task testPlugin
> Task :testPlugin
custom plugin task executing.
Result: Stringtotest
Sorry to add this as an answer but I don't have enough points to comment (yes it is a bit late in coming but I found this in a search and it came so close, maybe this will help someone else).
The answer by #M.Ricciuti is correct, just missing one file, namely a settings.gradle in the referencing project (not the plugin) directory:
pluginManagement {
repositories {
maven {
url '../repos/maven-repo'
}
gradlePluginPortal()
ivy {
url '../repos/ivy-repo'
}
}
}
Many thanks, I have tried many things that didn't work before finding this, even the examples by gradle didn't work (or more likely I didn't run them correctly). Anyway I merged what I saw in the answers with M. Ricciuti's answer and saw that file in the sample.
My complete project is at https://github.com/reddierocket/sampleGradlePlugin
The readme has instructions to run it. (Note I did not include the wrapper but I am using gradle version 5.3.1.)

Compiling GRPC server/client

I'm having a lot of trouble figuring out how to compile a GRPC Java server. I looked all over the grpc.io website and closest thing I found was this: http://www.grpc.io/docs/#quick-start , where I run
../gradlew -PskipCodegen=true installDist to build, and
./build/install/grpc-examples/bin/hello-world-client to run the client. This all works, but only for the hello-world tutorial. I have no idea how to do this for my own client/server. I'm able to generate the client/server protobufs using the .proto file. I looked in their readme and Java tutorial and couldn't find out how to compile the actual server (and client) after I write them
https://github.com/grpc/grpc-java/blob/master/examples/README.md
(can't link java tutorial because I dont have enough reputation). Unless there's documentation im missing, does anyone know how to compile a server and client that implements the GRPC classes generated from the .proto file? I did spend a fair amount of time searching. Any advice is much appreciated, thanks.
Also have a similar problem, ensure that:
You configured correctly the protoc, that will be downloading the executable and configure it as an environment variable of your OS.
The build.gradle file, make sure to include protobuf-gradle-plugin:
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'com.google.protobuf'
ext.grpcVersion = '1.0.1'
ext.protobufVersion = '3.0.2'
buildscript {
repositories { mavenCentral() }
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0' }
}
repositories {
mavenCentral()
}
dependencies {
compile "com.google.protobuf:protobuf-java:${protobufVersion}"
compile "io.grpc:grpc-all:${grpcVersion}"
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protobufVersion}" }
plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } }
generateProtoTasks { ofSourceSet('main')*.plugins { grpc { } } }
}
idea {
module {
sourceDirs += file("${protobuf.generatedFilesBaseDir}/main/java");
sourceDirs += file("${protobuf.generatedFilesBaseDir}/main/grpc");
}
}
Your proto file:
syntax = "proto3";
package com.company.project;
service CompanyService{
rpc call(RequestMessage) returns (ResponseMessage) {}
}
Run gradle clean build, and it should generate your service and client classes for CompanyService.
The idea plugin, is jut for telling IntelliJ to recognize the src/main/proto as a source set.
To actually execute the client and server, you will need to make the implementation, mentioned in the tutorial for gRpc, and then apply the application plugin, in order to generate correctly the executable jar
//build.grdle code...
apply plugin: 'application'
mainClassName = 'com.company.project.MainClass'
jar { manifest { attributes('Main-Class' : mainClassName) } }
I had a similar issue but solved it using it in Gradle by adding the 'application' plugin. Before I was using the 'java' plugin and I could only generated a jar file. After switching to the 'application' plugin there is a gradle task similar to the gRPC example.
./gradlew installDist
And now to start your server you can run something similar to this:
./build/install/your-project/bin/your-server
To actually generate the Java classes off my .proto files I needed to run './gradle build' and also include the source generated using the sourceDir element you can see in the build.gradle below.
This is the full build.gradle file.
apply plugin: 'application'
apply plugin: 'com.google.protobuf'
apply plugin: 'idea'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.6'
}
}
repositories {
jcenter()
}
dependencies {
compile 'io.grpc:grpc-all:0.14.0'
}
mainClassName = "com.domain.service.YourMainClass"
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.0.0-beta-2"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:0.14.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
idea {
module {
sourceDirs += file("${projectDir}/build/generated/source/proto/main/grpc");
sourceDirs += file("${projectDir}/build/generated/source/proto/main/java");
}
}
I am new to gRPC so any improvments to my Gradle file would be appericated.
This question has been answered on groups.google.com by 'Eric Anderson':
The JAR only has your code in it. It sounds like you want to make a "fat" jar which includes all your dependencies. You can do something like this:
jar {
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
Note that that isn't gRPC-specific; it's just working with Gradle. There may be alternatives, such as a quick Googling returned gradle-fatjar-plugin.

Use Java class in Gradle build script

I have a Gradle build script which has to instantiate a Java class in a Task and call a method on the created object. Currently, I have the following:
apply plugin: 'java'
dependencies {
compile files("libs/some.library.jar")
}
task A << {
def obj = new some.library.TestClass()
obj.doSomething()
}
The problem is that the class some.library.TestClass() is not found. I read this article about how to use Groovy classes in Gradle, but I need my Java class to come from an external JAR file. How can I add a jar to the build source? It seems that the dependencies block doesnt do what I expect it to do. Can anyone give me a hint in the right direction?
The dependency compile files("libs/some.library.jar") is added as a project dependency not as the script dependency itself. What You need to do is to add this dependency in script's classpath scope.
apply plugin: 'java'
buildscript {
dependencies {
classpath files("libs/some.library.jar")
}
}
task A << {
def obj = new some.library.TestClass()
obj.doSomething()
}
Now it should work.

Categories