Problem when auto generating gRPC stub files using Gradle - java

I've been asked to implement some gRPC classes for a college course, and have run into some problems when generating the java classes from one source proto file.
Some background first: it's a fairly basic service, with a simple method that receives an id and returns a phone and an email. This is the proto file (BuscarData means FetchData, sorry for the leftover non translation!):
syntax = 'proto3';
option java_multiple_files=true;
option java_generic_services= true;
package uy.edu.um.sd20;
message DataRequest {
int32 id = 1;
}
message DataResponse {
string phone = 1;
string email = 2;
}
service DataRepo {
rpc BuscarData (DataRequest) returns (DataResponse);
}
The idea I had was to generate the classes with gradle plugins. My build.gradle:
plugins {
id 'java'
id "com.google.protobuf" version '0.8.8'
}
apply plugin: 'java'
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.11.4'
implementation 'io.grpc:grpc-netty-shaded:1.29.0'
implementation 'io.grpc:grpc-protobuf:1.29.0'
implementation 'io.grpc:grpc-stub:1.29.0'
}
sourceSets {
main {
proto {
srcDir 'src/main/proto'
}
java {
srcDirs 'src/main/java', 'generated-sources/main/java'
}
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.11.0'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0'
}
}
generateProtoTasks.generatedFilesBaseDir = 'generated-sources'
generateProtoTasks {
all().each { task ->
// Here you can configure the task
}
ofSourceSet('main')
}
}
From what I understood, everything's there: the grpc and protoc dependencies, and the plugin which enables protoc to compile grpc (protoc-gen-grpc), and where to deposit the generated files.
However, there are two problems:
the generated-sources are not marked as source or anything like that, meaning they cannot be imported from other classes
if I'm not mistaken, the generated-sources should generate a skeleton of DataRepoImpl so that I can add the code needed for BuscarData. However, it didn't. Or maybe I should create it, extending from DataRepo.java, but I couldn't test it, due to problem n°1.
I've added a screenshot of the project file structure:
img
As you can see, quite a lot (if not all) of the gradle settings are copy-pasted and scavenged from many different web-sites. I hope I was careful enough not to repeat any imports. There are similar questions, and I tried the solutions there, but no luck there. One example, with which I knew I had to include the gen grpc plugin: another SO question
Any tip regarding anything else is welcome! I'm new to stackoverflow question-asking, so I may have made mistakes regarding the question specificity or aim of the question.
Thanks!
Franri.

For 1), the plugin should put the generated files are the input for java compile tasks even if you do not explicitly add 'generated-sources/main/java' in the sourceSets configuration. Version 0.8.8 has been a while, you can try with newer versions. There might have been some minor fixes for things you may hit.
For 2), you did not add grpc plugin to each generateProto task. It should be
generateProtoTasks {
all().each { task ->
task.plugins { grpc{} }
}
ofSourceSet('main')
}

Related

Running jar results in ClassNotFoundException (Gradle)

So I'm relatively inexperienced on how gradle works, and I need some help getting my jar working. My application generates some files through the terminal. However, when I try to run the jar, it gives me an error.
build.gradle:
plugins {
id 'java'
}
group 'me.tl0x'
version '1.0'
repositories {
mavenCentral()
}
jar {
manifest {
attributes (
'Main-Class': 'me.tl0x.Main'
)
}
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation 'org.freemarker:freemarker:2.3.29'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jline:jline:3.21.0'
implementation 'org.fusesource.jansi:jansi:2.4.0'
}
test {
useJUnitPlatform()
}
Error Message:
PS C:\Path> java -jar ./build/libs/FabricModGenerator-1.0.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/jline/terminal/TerminalBuilder
at me.tl0x.terminal.Interface.<init>(Interface.java:29)
at me.tl0x.Main.main(Main.java:48)
Caused by: java.lang.ClassNotFoundException: org.jline.terminal.TerminalBuilder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
... 2 more
Any help would be greatly appreciated.
It's basically because your classpath doesn't include "jline" and the other stuffs required by your application.
Just use the application plugin and let that do the right thing (this is the right answer...)
But if it's a utility thing that you're doing...
Create a task that does the right thing and just run it via gradle (since you have no arguments)
def asFileUrl(filepath) {
return "file:///" + new java.net.URI(null, filepath, null).toASCIIString();
}
task LauncherJar(type: Jar) {
appendix = "launcher"
ext.launcherClasspath = { ->
def verifyLibs = [
configurations.runtimeClasspath.collect { asFileUrl(it.getCanonicalPath()) }.join(' '),
asFileUrl(jar.archivePath.getCanonicalPath())
]
return verifyLibs.join(' ')
}
manifest {
attributes ("Class-Path": launcherClasspath())
}
}
task execMyJar(type: JavaExec, dependsOn: [jar, LauncherJar]) {
group = 'Execution'
description = 'Do The thing that needs doing'
classpath = files(LauncherJar.archivePath)
mainClass = 'me.tl0x.Main'
}
Then you can just do gradle execMyJar.
Note that here, I'm creating a launcher jar with the Class-Path element in the Manifest file. The reason for that is to avoid the situation (Windows only?) where your command line is too long... It might not matter in your case since you haven't got that many dependencies listed (but I don't know about transitive dependencies).

How to merge source sets while sharing dependencies to each other

I'd like to publish a library with two different API versions where both use the same core code underneath. I tried shading/shadowing but have struggles getting the visibility right (I'd like to hide the core code from the API user). So I wanted to achieve my goals by having different source sets and configurations:
sourceSets {
// the `main` source set acts as the common code base for `api` and `api2`
api {
java {
srcDir 'src/api/java'
// Includes classes from `main`:
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
api2 {
java {
srcDir 'src/api2/java'
// Includes classes from `main`:
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
}
configurations {
common {
canBeResolved = true
canBeConsumed = false
}
// These art the configurations used both for being consumed with `project(...)` or published:
exposedApi {
canBeResolved = true
canBeConsumed = true
extendsFrom common
}
exposedApi2 {
canBeResolved = true
canBeConsumed = true
extendsFrom common
}
}
task apiJar(type: Jar) {
group = 'build'
from configurations.exposedApi
baseName = 'api'
}
task api2Jar(type: Jar) {
group = 'build'
from configurations.exposedApi2
baseName = 'api2'
}
publishing {
publications {
api(MavenPublication) {
artifact apiJar
artifactId 'mylib-api'
}
api2(MavenPublication) {
artifact api2Jar
artifactId 'mylib-api2'
}
}
}
dependencies {
common sourceSets.main.output
exposedApi sourceSets.api.output
exposedApi2 sourceSets.api2.output
}
If I want to use one of these APIs I can easily use project(path: ':mylib', configuration: 'exposedApi2') or use one of the published Maven artifacts and it works nicely.
But as soon as I change classes in the main source set to internal in order to achieve proper encapsulation of the main code, the API code won't compile anymore:
Cannot access 'SomeClassInMain': it is internal in '' (<-- yes, it really shows nothing in the '')
I also tried to merge the source set into one, so there is technically not really a main source set anymore:
sourceSets {
api {
java {
srcDirs('src/api/java', 'src/main/java')
}
}
api2 {
java {
srcDirs('src/api2/java', 'src/main/java')
}
}
}
That now works all as intended, no compilation errors, calls from the API to main work as expected and the classes in main even have internal visibility. But unfortunately IntelliJ seems to not pick up the fact that classes in main are really part of the same source set. I get an error (Unresolved reference: SomeClassInMain) in the IDE every time I mention a class from the main sources and of course no auto-completion would work, too, making the solution somehow not really practical in the end.
So just to sum up the goal:
it's important that the main sources are accessible to the API
but not to the user using the API (or the Maven publication) – the only thing the user should be facing is the API
If possible, I'd like to not put the API and main code in separate modules and publish them separately for encapsulation reasons
I tried a shading/shadowing (fat/uber JAR) approach but I haven't managed to reduce the visibility to internal in the main sources
I'm new to the topic of these complicated kinds of build configurations so maybe I simply have chosen the wrong approach. Maybe there's a better one which I haven't yet managed to find?
Many, many thanks in advance!

gradle Jigsaw module not found

I try to run a very simple gradle project which uses java 9 modules, but i receive the following error.
/home/vadim/IdeaProjects/test_modules/src/main/java/module-info.java:2: error: module not found: HdrHistogram
requires HdrHistogram;
^
Here is it https://github.com/vad0/test_modules.
The main class does basically nothing.
package app;
import org.HdrHistogram.Histogram;
public class RunHdr {
public static void main(String[] args) {
final Histogram histogram = new Histogram(5);
System.out.println(histogram);
}
}
It uses only one dependency: HdrHistogram. I included this magic command in build.gradle according to official gradle tutorial https://docs.gradle.org/current/samples/sample_java_modules_multi_project.html.
java {
modularity.inferModulePath = true
}
The whole build.gradle looks like this.
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
java {
modularity.inferModulePath = true
}
dependencies {
compile group: 'org.hdrhistogram', name: 'HdrHistogram', version: '2.1.12'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
module.info looks like this
module test.modules.main {
requires HdrHistogram;
}
I have already read a number of tutorials on Jigsaw and a whole bunch of stackoverflow questions related to it, but still can't make this simple example work. How do i fix it?
Thank you
Unfortunately, gradle does not treat every jar as a module (in simple words). If you want to find out how exactly is gradle building the module-path (as opposed to class-path), you probably want to start from here, specifically at the isModuleJar method. It's pretty easy to understand (though it took me almost two days to set-up gradle and debug the problem out) that the dependency that you are trying to use : gradle says that it is not a module (it isn't wrong, but I am not sure it is correct either). To make it very correct, gradle will add your dependency to the CLASSPATH, but in the very next line: it will not add your dependency to the module-path, because if fails the filter in isModuleJar.
I do not know if this is a bug or not, or may be this is on purpose, but the solution is easy:
plugins.withType(JavaPlugin).configureEach {
java {
modularity.inferModulePath = true
}
tasks.withType(JavaCompile) {
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
]
classpath = files()
}
}
}
you add it to the path, on purpose. I will flag this as a defect and let's see what they have to say.
EDIT
Even better, use a plugin that is written by a gradle commiter:
plugins {
id 'java'
id 'de.jjohannes.extra-java-module-info' version "0.1"
}
And the easiest option on your case is to do :
extraJavaModuleInfo {
automaticModule("HdrHistogram-2.1.12.jar", "HdrHistogram")
}

aspectj throwing ClassCastException

I am trying to build some java 1.6 application with aspects after migrating the build from some older gradle version to gradle 6.3. Because of the newer gradle version, I am using java 9.
The aspect compiler throws ClassCastException (link to log below), which, if I understand correctly, is caused by compiler not finding java.lang.Object class. I am only guessing it might something to do with using java 9 to run the build, but targeting java 1.6 compatibility. If this is the case, how do I tell the compiler where to find it? I tried to pass the -1.6 argument to ajc, but nothing changed.
I also include my build.gradle contents - I apologize it is not fully in gradle 6.3, because I got stuck on this aspectj compilation problem, before migrating it fully.
Thanks in advance for every response.
build.gradle contents:
buildscript {
repositories {
maven {
url "${artifactoryURL}/libs-releases"
}
}
dependencies {
classpath "io.freefair.gradle:aspectj-plugin:5.0.0-rc6"
classpath "gradle.plugin.org.myire:quill:2.3.1"
}
}
apply plugin: 'java'
apply plugin: 'io.freefair.aspectj.post-compile-weaving'
apply plugin: 'org.myire.quill.cobertura'
apply from: 'https://localnet/public/gradle/trunk/repositories.gradle'
java {
sourceCompatibility = JavaVersion.VERSION_1_6
}
group = "cz.svt"
version = "${version}"
aspectj {
version = "1.6.8"
}
configurations.all {
transitive = false
}
dependencies {
compile('avalon:avalon:4.2.0')
compile('cglib:cglib-nodep:2.2')
compile('commons-beanutils:commons-beanutils:1.7.0')
compile('commons-codec:commons-codec:1.3')
compile('commons-collections:commons-collections:3.1')
compile('org.apache.commons:commons-compress:1.4.1')
compile('commons-digester:commons-digester:1.6')
compile('commons-fileupload:commons-fileupload:1.1')
compile('commons-io:commons-io:1.3.1')
compile('commons-lang:commons-lang:2.0')
compile('commons-logging:commons-logging:1.0.4')
compile('commons-net:commons-net:2.0')
compile('commons-pool:commons-pool:1.2')
compile('commons-validator:commons-validator:1.1.3')
compile('com.ibm.db2:jdbc3-driver:9.5')
compile('dom4j:dom4j:1.6.1')
compile('org.codehaus.groovy:groovy-all:1.5.7')
compile('org.apache.xmlgraphics:fop:0.94')
compile('com.google.guava:guava:13.0.1')
compile('org.codehaus.groovy:groovy-all:1.5.7')
compile('cz.svt:hibernate-svt:3.6.8.Final')
compile('com.itextpdf:itextpdf:5.0.2')
compile('joda-time:joda-time:2.1')
compile('javax.servlet:jstl:1.0.6')
compile('junit:junit:3.8.1')
compile('log4j:log4j:1.2.15')
compile('mail:mail:1.0.3')
compile('org.springframework:spring-core:1.2.9')
compile('org.springframework:spring-beans:1.2.9')
compile('org.springframework:spring-mock:1.2.9')
compile('taglibs:standard:1.0.6')
compile('struts:struts:1.2.4')
compile('struts:struts-el:1.2.4')
compile('strutstest:strutstest:2.1.3-1.2-2.3')
compile('velocity:velocity:1.4')
compile('org.apache.ant:ant:1.7.1')
compile('org.apache.tomcat:jsp-api:6.0.18')
compile('org.apache.tomcat:servlet-api:6.0.18')
compile('org.apache.tomcat:catalina:6.0.18')
compile('org.apache.tomcat:dbcp:6.0.18')
compile('org.easymock:easymock:3.0')
compile('cz.svt:easymock-propeq:1.3')
compile('org.testng:testng:5.11:jdk15')
testCompile('org.apache.ftpserver:ftplet-api:1.0.0')
testCompile('org.apache.ftpserver:ftpserver-core:1.0.0')
testCompile('org.apache.mina:mina-core:2.0.0-M4')
testRuntime('org.slf4j:slf4j-api:1.6.1')
testRuntime('org.slf4j:slf4j-log4j12:1.6.1')
testRuntime('org.uncommons:reportng:1.0')
testRuntime('asm:asm:3.0')
testRuntime('asm:asm-tree:3.0')
testRuntime('org.hibernate:hibernate-commons-annotations:3.2.0.Final')
testRuntime('org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final')
testRuntime('javax.transaction:jta:1.1')
testRuntime('antlr:antlr:2.7.6')
testRuntime("org.objenesis:objenesis:1.2")
testRuntime("oro:oro:2.0.7")
}
test {
useTestNG() {
suites 'src/test/resources/testng.xml'
listeners << 'org.uncommons.reportng.HTMLReporter'
listeners << 'org.testng.reporters.XMLReporter'
}
}
task reports {
dependsOn coberturaTestReport, javadoc
}
here is link to aspectj compiler output 1

Gradle generates Querydsl metadata twice via different annotation processors

I have a gradle build script. I want said script to generate QueryDSL-Metadata. Those metadata should be generated under the build/generated-sources/metamodel Folder.
The problem I am facing at the moment is that the metamodel is not only being generated once, but twice. Along with the desired target it is also being generated in the "default" buld/classes/... resulting in a "duplicate class"-error.
sourceSets {
generated.java.srcDirs=['build/generated-sources/metamodel']
main {
java { srcDir 'src/main/java' }
}
test {
java { srcDir 'src/main/test' }
}
}
configurations { querydslapt }
dependencies {
compile 'org.hibernate:hibernate-entitymanager:5.2.3.Final',
'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final-redhat-1',
'com.querydsl:querydsl-jpa:4.1.3',
// ... others, non-hibernate/querydsl ...
querydslapt 'com.querydsl:querydsl-apt:4.1.3'
}
task generateSources(type: JavaCompile, group: 'build', description:'Generates the QueryDSL query types') {
source = sourceSets.main.java
classpath = configurations.compile + configurations.querydslapt
options.compilerArgs = ['-proc:only',
'-processor', 'com.querydsl.apt.hibernate.HibernateAnnotationProcessor']
destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}
compileJava {
dependsOn generateSources
source generateSources.destinationDir
}
According to the gradle trace, the Problem appears to be that there are two AnnotatioProcessors in the mix. First, the HibernateAnnotationProcessor. Second, a JPAAnnotationProcessor, eventually generating the duplicate class. And I can't figure out why, the build script looks ok-ish. I know, it might be guesswork, but I am grateful for any suggestions. I even cleaned my gradle-cache, just in case. It might not even be a pure build-script related issue, but the behavior persists even if I run the script via console.
Gist, basically exactly what I "should" need
(older) Post regarding this issue
This thread's solution works for me, the idea is to hook the Annotation Processor into the javac, the HibernateAnnotationProcessor can be declared via compilerArgs, roughly like:
dependencies {
compile 'org.hibernate:hibernate-entitymanager:5.2.3.Final',
'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final-redhat-1',
'com.querydsl:querydsl-jpa:4.1.4',
'com.querydsl:querydsl-apt:4.1.4',
// other
}
ext {
generatedSourcesDir = file("build/generated-sources/metamodel")
}
sourceSets {
main {
java {
srcDir 'src/main/java'
srcDir generatedSourcesDir
}
}
test {
java { srcDir 'src/main/test' }
}
}
compileJava {
doFirst {
generatedSourcesDir.mkdirs()
}
options.compilerArgs += ['-s', generatedSourcesDir,
'-processor', 'com.querydsl.apt.hibernate.HibernateAnnotationProcessor']
}
But I still wonder why the first approach does not work (runs two annotation processors), so any idea is still highly appreciated.

Categories