Status: Unimplemented
Author: kchodorow@
Users are annoyed by having to specify all deps in their WORKSPACE file. To avoid this inconvenience, Bazel could load subprojects' WORKSPACE files automatically.
@guava
and @com_google_guava
).When a repository is defined in multiple files, which definition “wins”? What causes a conflict/error?
This definition wins, regardless of other definitions.
Note: as an intermediate step, this can be disabled, but the end goal is to allow this so that intermediate dependencies that the top level doesn‘t care about don’t need to be resolved.
Suppose we have a main repository that depends on repo x, and x depends on repo y:
In this case, version 1 of “foo” wins. This way, if a library has already figured out which version works for them, its reverse dependencies do not have to think about it.
This will also work if a parent overrides its children's versions, even if it has multiple children.
If there is no obvious hierarchy and multiple versions are specified, error out. Report what each chain of dependencies was that wanted the dep and at which versions:
In this case, Bazel would error out with:
ERROR: Conflicting definitions of 'foo': bazel-external/y/WORKSPACE:2 repository(name = 'foo' version = '1') requested by bazel-external/x/WORKSPACE:2 repository(name = 'y') requested by WORKSPACE:3 repository(name = 'x') vs. bazel-external/a/WORKSPACE:2 repository(name = 'foo' version = '2') requested by WORKSPACE:2 repository(name = 'a')
This is also the case with diamond dependencies:
This would print:
ERROR: Conflicting definitions of 'foo': bazel-external/x/WORKSPACE:2 repository(name = 'foo' version = '2') requested by WORKSPACE:2 repository(name = 'x') vs. bazel-external/z/WORKSPACE:2 repository(name = 'foo' version = '1') requested by bazel-external/y/WORKSPACE:2 repository(name = 'z') requested by WORKSPACE:3 repository(name = 'y')
I think that this should be fairly straightforward, as any repository used by the main repository or any subrepository had to be declared in the WORKSPACE already, so it will take precedence.
To be extra safe, we can start with adding a recursive = False
attribute to the workspace
rule, which we can then flip the default of.
There are two options for implementing this:
I think users will rebel unless we go with Option 2. However, this can have some weird effects: suppose we have the diamond dependency above, and the user's BUILD file contains:
cc_library( name = "bar", deps = ["@x//:dep"], # using @foo version 2 ) cc_library( name = "baz", deps = ["@y//:dep"], # using @foo version 1 )
If they build :bar and their coworker builds :baz, the two builds will work and get different versions of @foo. However, as soon as one of them tries to build both, they'll get the version mismatch error.
This is suboptimal, but I can't think of a way that all three of these can be satisfied:
This could be enforced by a CI on presubmit, which I think is good enough.
Whenever Bazel creates a new repository, it will attempt to parse the WORKSPACE file and do Skyframe lookups against each repository name. If the repository name is not defined, it will be initialized to the current WORKSPACE's definition. If it already exists, the existing value will be compared.
For now, we'll be very picky about equality: maven_jar
and new_http_archive
of the same Maven artifact will count as different repositories. For both native and skylark repository rules, they will have to be equal to not conflict.
One issue is that is a little tricky but I think will work out: the WORKSPACE file is parsed incrementally. Suppose the main WORKSPACE loads x.bzl, which declares @y and @z. If @y depends on @foo version 1 and @z depends on @foo version 2, this will throw a Skyframe error, even if @foo is later declared in the WORKSPACE file. However, this should be okay, because if these dependencies actually need @foo, it would need to be declared before them in the WS file already.
Not strictly required, but as part of this I'm planning to implement:
bazel-external
convenience symlink (to the [output_base]/external
directory) so users can easily inspect their external repositories.Questions users might have.
Where did @x come from?
Bazel will create a bazel-external/@x.version
should contain the WORKSPACE (or .bzl file) where we got @x's def and other WORKSPACE files that contain it.
Which version of @x is going to be chosen?
See resolution section above. Perhaps people could query for //external:x?
I want to use a different version of @x.
Declare @x in your WORKSPACE file, it'll override “lower” rules.
When I update @x, what else will change?
Because @x might declare repo @y and @y‘s version might change as well, we’d need a different way to query for this. We could implement deps() for repo rules or have some other mechanism for this.
Moving towards the user-as-conflict-resolver model (vs. the user-as-transcriber-of-deps model) means that repositories that the user may not even be aware of might be available in their workspace. I think this kind of paves the way towards a nice auto-fetch system where a user could just depend on @com_google_guava//whatever
in their BUILD file, and Bazel could figure out how to make @com_google_guava
available.
So you want to write a package manager does a good job outlining many of these challenges, but their suggested approach (use semantic versioning for dependency resolution) cannot be used by Bazel for the general case.