3

I'm trying to use Protobuf with Google's official Gradle plugin, with Gradle, in VS Code, in a small Java application to test out the workflow.

I can't get VS Code to recognize the generated Java source code, and in the terminal, I can't get Gradle to recognize the code either when I try to use Gradle to build the application.

VS Code displays this when I try to reference the generated code:

VS Code, editing a Java file called App.java, where the text "Person" displays a red underline

And Gradle displays the following error when I try to do a build:

> Task :app:compileJava FAILED
/home/mattgbi/vscode-gradle-protobuf-repro/app/src/main/java/vscode/gradle/protobuf/repro/App.java:14: error: cannot find symbol
        System.out.println(Person.class);
                           ^
  symbol:   class Person
  location: class App
1 error

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 405ms
4 actionable tasks: 1 executed, 3 up-to-date

I understand that I need to add an import in App.java to use the Person class. But when I try to invoke the VS Code code completion tool to do that, as described above, the code completion fails to help me do that.

However, if I manually type out the import statement, VS Code says it can't find it.:

VS Code error saying it can't find the package

However, at this point, Gradle is able to use the import statement (even though VS Code rendered errors) and is able to complete the build:

> ./gradlew build

> Task :app:compileJava
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

BUILD SUCCESSFUL in 1s
13 actionable tasks: 10 executed, 3 up-to-date

Here is a GitHub repo with a complete reproduction of the problem: https://github.com/mattwelke/repros/tree/main/vscode-gradle-protobuf-repro

Here are the steps I completed to encounter this issue:

  1. Create project with gradle init. Choose "application" and leave all settings as default.
  2. Follow instructions at https://github.com/google/protobuf-gradle-plugin to add the plugin to my project. Add "buildscript" part to top of build.gradle file, above plugins part of the file. Add line inside plugins part with id "com.google.protobuf" version "0.8.18", below the line id 'application' in that part. Add block starting with protobuf { to bottom of the file, so that I can have Gradle download and use its own protoc binary.
  3. Follow Java tutorial for Protobuf (https://developers.google.com/protocol-buffers/docs/javatutorial), add the code that has message Person { near the top of the page to my application in the new file app/src/main/proto/message.proto.
  4. Build with ./gradlew build in VS Code terminal.
  5. Observe source code generated into app/build/generated/source even though there were errors in the console saying package com.google.protobuf does not exist.
  6. Add dependency protobuf-java (from https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java) to build.gradle file under dependencies section of the file, so that my applications generated Java source code would be able to reference Protobuf library. Chose version 3.21.1 because it's the latest version as of right now. Added using implementation syntax.
  7. Build application again with ./gradlew build in VS Code terminal. Observed no errors this time in terminal.
  8. Try to write source code in App.java (under src) that references the generated Person class. VS Code can't find it.
  9. Try to build application again with ./gradlew build in VS Code terminal. Gradle can't find the source code either.
  10. Manually add import statement to App.java. Observe VS Code displaying errors about not being able to find the package still.
  11. Try to build application again with ./gradlew build in VS Code terminal. Gradle can't find the source code either. This time Gradle succeeds. Only issue remaining is how the IDE recognizes all the code (desired is that all code, including generated code, is recognized).
Matt Welke
  • 1,441
  • 1
  • 15
  • 40
  • It seems that you didn't import a package. Can you show the source-code of `Person`? – Lajos Arpad May 30 '22 at 22:34
  • Source code of Person.java: https://gist.github.com/mattwelke/5f3aae7611019f64123b7b1af63458e6 I chose not to include the generated code in the repro repo because in theory, the Gradle config should enable it to be reproduced exactly by a Gradle build for anyone cloning it. – Matt Welke May 30 '22 at 22:46
  • Your advice reminded me I could have just manually typed in the import statement instead of relying on my IDE's code completion features to add it for me. I did that, and got further. Now, VS Code displays errors (as if it were unaware of the generated source code's existence), but Gradle is able to build the application. I edited the question to add this. – Matt Welke May 30 '22 at 22:53
  • From the information you have provided it seems that the package is not seen as a resource by the project. – Lajos Arpad May 30 '22 at 23:03
  • Yeah that sounds about right. I'm wondering now what the right thing to do is. Should I investigate how to change VS Code's configuration so that it sees the generated code, leaving the Protobuf plugin's config alone to have it put the generated code in its default destination. Or, should I leave VS Code alone and override the Protobuf plugin in `build.gradle` to change where it puts the source code? And if so, where should I put the source code? I think usually you wouldn't check in generated code. So I'm thinking if I went with the latter, I wouldn't put the generated code anywhere in `src`. – Matt Welke May 30 '22 at 23:35
  • 1
    Have you installed the [Gradle Language Support](https://marketplace.visualstudio.com/items?itemName=naco-siren.gradle-language) extension. Re-open the project after installing this extension and the Gradle extension will work. – JialeDu May 31 '22 at 07:25
  • This didn't help. I tried both this extension and the Microsoft Gradle extension. Both resulted in the same thing after I did the "Java: Clean Language Server Workspace" command, which appears to reload the projects (I got a popup in the bottom right corner saying it imported a Java project). I noticed too that there's a settings view I can access by clicking on the three dots under "JAVA PROJECTS" in the bottom left corner, then clicking "Configure Classpath". It displays a list of Sources with four entries: "src/main/java", "src/main/resources", "src/test/java", "src/test/resources". – Matt Welke May 31 '22 at 21:41
  • I figured I'd try adding a fifth item to that list, which would be "app/build/generated/source/proto/main/java". But it won't let me edit that list and says "Read-only" next to "Sources". It also says "Below settings are only editable for projects without build tools. For Gradle project, please edit the entries in build.gradle." on that page. So I'm thinking that because I'm using Gradle, I'm meant to configure Gradle to recognize all the source code I intend to use in my app, including generated code. – Matt Welke May 31 '22 at 21:43
  • I find it odd that this plugin doesn't configure this for me, so that any IDE using Gradle for the project would automatically pick up the source code. Do you think I should file a bug report with the plugin maintainers? – Matt Welke May 31 '22 at 21:43
  • I got it to work. When I read the README for the plugin again, they describe how it adds a source set to Gradle as it is invoked. So I figured the problem is that it's only automatically adding the source set during the Gradle lifecycle, not when VS Code tries to parse the code to enable code completion. I added a source set whose `srcDir` was the directory the generated code goes into, then cleaned my Java workspace, and it worked. I'll add an answer. – Matt Welke May 31 '22 at 22:14

1 Answers1

4

My problem was that I didn't configure Gradle (via build.gradle) to include a new source set for where the generated code goes. VS Code reads the Gradle config when it detects it in order to set the classpath, and disables manually tweaking the class path when it detects it, so configuring Gradle is the only option for this.

Before adding source set to Gradle config:

accessing classpath settings before adding source set to Gradle config 1

accessing classpath settings before adding source set to Gradle config 2

accessing classpath settings before adding source set to Gradle config 2 3

After adding this to build.gradle at the bottom:

sourceSets {
  main {
    java {
      srcDir 'build/generated/source/proto/main/java'
    }
  }
}

after adding to classpath via Gradle settings 1

after adding to classpath via Gradle settings 2

Also, after deleting the app/build directory and running ./gradlew build again, I found that I had to run the "delete workspace" command from the Java extension in order to get my workspace back to a healthy state, where the generated code was recognized by VS Code.

I realized this was the issue after reading the README for the plugin (https://github.com/google/protobuf-gradle-plugin) and reading their instructions on overriding the source sets. Being somewhat new to Gradle, I forgot about the significance of source sets. The README describes how the plugin dynamically sets up a source set for the generated code directory.

I figured that because VS Code isn't invoking any Gradle tasks to do Java code completion etc (I think it uses Eclipse tooling), but because it apparently uses the Gradle config to configure how it behaves with Java, the key to solving my problem was to manually tell VS Code about the new source set by specifying it explicitly in my build.gradle file.

Matt Welke
  • 1,441
  • 1
  • 15
  • 40