Why not export all deps?

1,430 views
Skip to first unread message

Dan Fabulich

unread,
Feb 15, 2016, 5:12:26 PM2/15/16
to bazel-discuss
I'm porting a Maven build to Bazel, and finding that, in practice, I'm listing out all of my dependencies twice, once as deps and then again as exports.

1) Is there any harm in doing this? I think exporting "too much" won't affect build performance, will it?

2) Why can't java_library automatically export all deps? For example, I could imagine a boolean "export_deps" attribute, which, if true, would export all deps.

I could even imagine exporting all deps by default, which would match Maven's behavior. Why not?

Justine Tunney

unread,
Feb 15, 2016, 6:34:53 PM2/15/16
to Dan Fabulich, bazel-discuss
Generally speaking, a java_library that specifies exports should never have the srcs or deps attributes.

But let me share with you how I've been configuring my Maven dependencies in Bazel. I'm not sure if this is the "proper one true way™" since I'm not a Bazel developer. But I have three years of experience using Bazel and this seems to work fine for me.

Let's my project depends on com.google.template:soy:2016-01-12 which has a lot of transitive dependencies. Bazel doesn't fetch transitive jars from Maven (which is IMHO a good thing.) So I'll add each and every one of them to my WORKSPACE file manually. Then I'll create a file called third_party/java/soy/BUILD that looks like the following:

package(default_visibility = ["//visibility:public"])

licenses(["notice"])  # Apache License 2.0

java_library(
    name = "soy",
    exports = ["@soy//jar"],
    runtime_deps = [
        "@aopalliance//jar",
        "@asm//jar",
        "@asm_analysis//jar",
        "@asm_commons//jar",
        "@asm_util//jar",
        "@guice//jar",
        "@guice_assistedinject//jar",
        "@guice_multibindings//jar",
        "@icu4j//jar",
        "//third_party:guava",
        "//third_party:jsr305",
        "//third_party:jsr330_inject",
    ],
)

This way my java_library packages that want to use soy, can just add "//third_party/java/soy" to their deps list, rather than repeating all thirteen of those jars.

The fact that I've specified the transitive jars as runtime_deps is important, because that means depending on this rule will not compromise the strictness of Bazel's dependency system. If a library depending on soy also uses guava, then Bazel will still force it to declare Guava as one of its dependencies.

--
You received this message because you are subscribed to the Google Groups "bazel-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bazel-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/bazel-discuss/4bd4b0ee-becd-4cab-b27c-bb9aa69bef3e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Erik Kuefler

unread,
Feb 15, 2016, 11:22:46 PM2/15/16
to bazel-discuss
I use a structure very similar to Justine for managing my external maven deps and agree that it works quite well. I can't quite tell from your question whether you're talking about doubling up on exports for external dependencies, or for internal dependencies from one package in your project to another. The reason that Bazel doesn't export internal dependencies automatically is an interesting subtlety in its design that is quite important for larger projects.

For Java, Bazel usually works best when each package has its own BUILD file and each BUILD file has one target (Pants calls this the 1:1:1 rule). This isn't strictly necessary though, and especially when porting over Maven repos it often makes sense to start by just defining BUILD files for top-level projects that glob everything underneath them recursively. This is bad for build performance though, since it means the entire project needs to be rebuilt whenever a small part of it changes. So let's assume you have a BUILD file for each package.

The question now is, supposing you have package A that depends on package B, and package B depends on a third-party library like guava, should package A be able to use guava also without having to explicitly say it depends on it? As far as the Java compiler is concerned this is fine, package B and guava must both be on the classpath during compilation, so there's no reason that A can't refer to guava. But Bazel will give you an error if you try unless you also list guava as a dependency of A (or if B exports guava). Why? It's to make B easier to maintain. Suppose B was refactored such that it didn't need to depend on guava any more, so the author tried to remove B's dependency on guava. Now A and a lot of other packages will break! A lot of people who depended on B might have been relying on it for the guava dependency without even realizing it, and now whoever's working on B would have to either find every package depending on B that needed guava and add it manually, or keep guava in B's dependencies (slowing down builds for everyone who depended on B but not on guava). Bazel actually used to work this way and it was pretty painful in a large company like Google. So this is why it makes you list your exports explicitly - exports are effectively part of your targets API and you can't change them without breaking people, whereas deps can be added and removed freely without causing problems for other people. In practice, it's pretty rare to use exports for your own packages (though there are definitely legitimate cases).

You can see evidence of this in Bazel's error messages too. Sometimes when you try to import a new class but haven't added the dependency you'll get the classic "Class not found" error from javac. Other times, you'll get the "missing transitive dependency" error from Bazel and no error from javac. The first case happens when you reference a class that isn't known to any of your dependencies, so neither javac nor Bazel have any idea what class you're talking about. The second case happens when the class you're looking for is referenced by one of your dependencies, so javac and Bazel know what it is, but Bazel's helpfully refusing to continue until you make your dependency on that class explicit so that other people can't accidentally break you.

Not sure if that's exactly what you were asking, but I hope it helps!

Damien Martin-guillerez

unread,
Feb 16, 2016, 4:04:37 AM2/16/16
to Erik Kuefler, bazel-discuss
There is also another point to consider: ijars

Interface Jars are created by Bazel to avoid useless recompilation. If an interface jar hasn't changed then dependent libraries don't need to be recompiled.

Let say that A depends on B who depends on C
if you modify C such as the interfaces of C doesn't change, then ijar(C) won't change and A and B won't be rebuilt. If you change C such as ijar(C) change, B will be rebuilt but ijar(B) might not change, in which case A won't be rebuilt.

Now say that B exports C, in the last case, A will be rebuilt because ijar(C) is part of the interface of B and trigger an invalidation of A.

--
You received this message because you are subscribed to the Google Groups "bazel-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bazel-discus...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages