Search code examples
javagradlevisual-studio-codeprotocol-buffersgradle-plugin

How do I configure VS Code and Gradle to use generated Java code with the Gradle Protobuf plugin?


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).

Solution

  • 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.