javabazelbazel-java

Why doesn't java_binary propagate runtime dependencies to java_test


I wrote a small application and used java_binary as follows:

java_binary(
    name = "MyCommand",
    srcs = [
        "MyCommand.java",
    ],
    visibility = ["//visibility:public"],
    deps = [
        "//src/main/java/com/example/two",
    ],
)

It depends on a java_library target //src/main/java/com/example/two

I then wrote a java_test as follows:

java_test(
    name = "TestMyCommand",
    size = "small",
    srcs = [
        "TestMyCommand.java",
    ],
    deps = [
        ":MyCommand",
    ],
)

The test is pretty simple and just does new MyCommand().

This unfortunately fails quickly with a ClassDefNotFoundException with a class file found in //src/main/java/com/example/two. Setting a breakpoint it looks like that library is not included in the ClassPath.

HOWEVER, if I change my java_test to depend on a java_library THEN the transitive dependency of //src/main/java/com/example/two is included.

I could not find anything in the documentation explaining why?


Solution

  • This is because java_binary is a "binary" rule, i.e., it outputs an executable, or another way, it's typically a "terminal" or "root" rule. So it doesn't pass along the info to add its deps to the final classpath, because it doesn't typically expect to be in the dependencies[1] of other targets (even java_test).

    You could refactor the BUILD file so that MyCommand.java is in a java_library, and the java_test and the java_binary targets depend on that:

    java_binary(
        name = "MyCommandMain",
        runtime_deps = [":MyCommand"],
        main_class = "com.example.one.MyCommand",
    )
    
    java_library(
        name = "MyCommand",
        srcs = [
            "MyCommand.java",
        ],
        visibility = ["//visibility:public"],
        deps = [
            "//src/main/java/com/example/two",
        ],
    )
    

    1: But you do sometimes see things like this:

    # or other kinds of binary rules (cc_binary, py_binary, etc)
    java_binary(
      name = "my_java_bin",
      ...,
    )
    
    sh_binary(
      name = "my_shell_script",
      srcs = ["script.sh"],
      data = [":my_java_bin"],
    )
    

    and script.sh runs my_java_bin as part of whatever it's accomplishing. The difference here is that my_shell_script is using the whole binary, rather than programming against code within it.