0

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?

Setheron
  • 3,520
  • 3
  • 34
  • 52

1 Answers1

1

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.

ahumesky
  • 4,203
  • 8
  • 12
  • how does the sh_binary work if it does not get the transitive dependencies also ? It needs them for runtime... Is there a flag to control this? I find this distinction strange... – Setheron Aug 04 '22 at 16:15
  • One of the outputs of a binary rule is its so-called "runfiles", the files needed to run the executable. The sh_binary receives those files (i.e., bazel sets up all those files in the sh_binary's exec root for `bazel run`), and in the case of java_binary, those are a wrapper script that invokes java with the right arguments and jars, and the collection of jars for the application. How the java rules decide what should be on the class path (compile time classpath, runtime classpath, or both) is separate. – ahumesky Aug 04 '22 at 16:58
  • Regarding a flag, what behavior specifically are you looking to change? – ahumesky Aug 04 '22 at 17:01
  • Propagate the runtime deps like java_library – Setheron Aug 04 '22 at 17:26
  • I'm pretty sure this is working as intended (e.g. https://github.com/bazelbuild/bazel/issues/11705 and https://github.com/bazelbuild/bazel/issues/12836), but maybe `java_test` depending on a `java_binary` is worth some consideration, so feel free to open a feature request: https://github.com/bazelbuild/bazel/issues/new?assignees=&labels=&template=feature_request.yml – ahumesky Aug 04 '22 at 19:32