can external dependency depend back on main project?

82 views
Skip to first unread message

m...@google.com

unread,
Jan 15, 2019, 11:45:53 AM1/15/19
to bazel-discuss
Is it possible for an external dependency to depend back on targets defined by the main project, when linking statically? Currently, when I try this, I encounter link-time errors due to multiple definitions of various symbols (one in bazel-out/k8-fastbuild/bin/external/foo/some/path/bar.pic.o, and the other in bazel-out/k8-fastbuild/bin/some/path/bar.pic.o, where "foo" is the name of the main project) [*].
I've read https://github.com/bazelbuild/bazel/issues/6969 and the "Bazel C/C++ Transitive Libraries - Attack Plan" at https://docs.google.com/document/d/1d4SPgVX-OTCiEK_l24DNWiFlT14XS5ZxD7XhttFbvrI/edit?usp=sharing, but am unclear whether those would address this, and if there is any workaround I can employ in the meantime.

[*] Example of an actual link error, just to provide a flavor:

/build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/external/local_config_cc/extra_tools/envoy_cc_wrapper -o bazel-out/k8-opt/bin/test/extensions/quic_listeners/quiche/dummy_test -pthread -pthread '-fuse-ld=gold' -Wl,-no-as-needed -Wl,-z,relro,-z,now -pass-exit-codes -Wl,--gc-sections -Wl,@bazel-out/k8-opt/bin/test/extensions/quic_listeners/quiche/dummy_test-2.params)
Execution platform: @bazel_tools//platforms:host_platform
/usr/bin/ld.gold: error: bazel-out/k8-opt/bin/source/common/common/_objs/assert_lib/assert.o: multiple definition of 'Envoy::Assert::ActionRegistrationImpl::debug_assertion_failure_record_action_'
/usr/bin/ld.gold: bazel-out/k8-opt/bin/external/envoy/source/common/common/_objs/assert_lib/assert.o: previous definition here


rodr...@google.com

unread,
Jan 15, 2019, 1:38:42 PM1/15/19
to bazel-discuss
On Tuesday, January 15, 2019 at 5:45:53 PM UTC+1, m...@google.com wrote:
> /build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/external/local_config_cc/extra_tools/envoy_cc_wrapper -o bazel-out/k8-opt/bin/test/extensions/quic_listeners/quiche/dummy_test -pthread -pthread '-fuse-ld=gold' -Wl,-no-as-needed -Wl,-z,relro,-z,now -pass-exit-codes -Wl,--gc-sections -Wl,@bazel-out/k8-opt/bin/test/extensions/quic_listeners/quiche/dummy_test-2.params)
> Execution platform: @bazel_tools//platforms:host_platform
> /usr/bin/ld.gold: error: bazel-out/k8-opt/bin/source/common/common/_objs/assert_lib/assert.o: multiple definition of 'Envoy::Assert::ActionRegistrationImpl::debug_assertion_failure_record_action_'
> /usr/bin/ld.gold: bazel-out/k8-opt/bin/external/envoy/source/common/common/_objs/assert_lib/assert.o: previous definition here

The problem here seems to be that the main workspace duplicates source code from the external workspace (in this case, envoy's assert.cc is duplicated in the main workspace). If I've understood correctly, you should be able to avoid this by removing assert.cc from your workspace and depending on @envoy//source/common/common:assert_lib.

I don't think that either of the approaches you referenced would help, since Bazel wouldn't know that the two copies of assert.cc define the same static variable.

m...@google.com

unread,
Jan 15, 2019, 2:27:51 PM1/15/19
to bazel-discuss

Thanks! I should have been a little clearer--there aren't two copies of assert.cc. The main project is "envoy", and a library in the external dependency is attempting to depend *back* on something in envoy, via a BUILD rule (in the external dependency) that has deps = ["@envoy//source/common/common:assert_lib"]. I readily admit this is a weird setup--without getting into details, it's to work around idiosyncracies in a 3rd-party library that currently expects some of its source files to come from the codebase that embeds it.

Maybe the answer is "that isn't supported" or "I'm doing it wrong", but first I wanted to check if there are known ways to get this to work.

Jay Conrod

unread,
Jan 15, 2019, 2:59:41 PM1/15/19
to m...@google.com, bazel-discuss
We saw something like this in Go. I reported it as bazelbuild/bazel#7099. Possibly the same problem?

There's a flag, --experimental_remap_main_repo, which might resolve this.

--
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/7579d792-bce1-4daf-805e-e4a9d7ce5c22%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

m...@google.com

unread,
Jan 15, 2019, 3:41:28 PM1/15/19
to bazel-discuss
Cool--thanks for the pointer! So, adding --experimental_remap_main_repo did not fix the problem, but then I noticed mention in one of the related issues (https://github.com/bazelbuild/bazel/issues/3115) of the repository identifier '@'. I tried using that instead of '@envoy' in the external dependency's build rule [*], and that fixed the linker error, both with and without --experimental_remap_main_repo.

This is great, though I wish I knew a little more about '@', to have more confidence that this is right solution. Do you know if there's documentation of it anywhere? I couldn't find anything on it in the Bazel external dependencies / repository rules / workspace rules documentation--all the examples there use '@' followed by a repository name.

Thanks again for the tip!

[*] deps = ["@//some/path:foo_lib"]

m...@google.com

unread,
Jan 15, 2019, 5:10:11 PM1/15/19
to bazel-discuss
Forwarding a very helpful response from a Bazel team member that was unintentionally sent only to me:

-----

Hi,
Using "@//some/path:foo_lib" to refer to a target that is in the main repository is the correct way to do that. That is exactly what the empty repository identifier ("@") is for; it always refers to the main repository.

But note that that means that what that label points to will change based on which repository you're referencing it from. To illustrate this with an example:

Suppose your repository is called "my_repo" and contains the following target:
my_repo/BUILD
java_library(
name = "my_lib",
srcs = ["MyLib.java"],
deps = ["@//some/path:lib"]
)

Then imagine the following two, separate Bazel projects with corresponding WORKSPACE files:
bar/WORKSPACE
workspace(name = bar)
local_repository(
name = "my_repo",
path = "../my_repo"
)

foo/WORKSPACE
workspace(name = foo)
local_repository(
name = "my_repo",
path = "../my_repo"
)

Both foo and bar contain the target //some/path:lib.

When you are in workspace bar and you build @my_repo//:my_lib, it will build with the dep @bar//some/path:lib (because bar is the main repo), and when you are in foo and build @my_repo//:my_lib, it will build with the dep @foo//some/path:lib.

In short, you're using it correctly, just be aware of the implications :)

==================================

For completeness, the purpose of the --experimental_remap_main_repo flag is different. When you are in a particular repository, let's say one called "my_repo", you can refer to labels within that repository in 3 "fully qualified" ways (you can also refer to labels using the relative syntax):
1. @my_repo//some/path:lib
2. //some/path:lib
3. @//some/path:lib

(1) and (2) will always refer to labels withing the repo "my_repo". As shown above, (3) will only refer to labels within my_repo if Bazel is being run from my_repo. If my_repo is used as an external repository from a different repository, then when invoked from that other repository, (3) would refer to that repository.

Currently there is a bug in Bazel where @my_repo//some/path:lib and //some/path:lib are actually treated as two different labels which causes a number of problems. The flag --experimental_remap_main_repo fixes that bug and causes those two labels to be treated the same.

m...@google.com

unread,
Jan 16, 2019, 2:32:00 PM1/16/19
to bazel-discuss
Well, it looks like I celebrated prematurely. While it is true that changing the external dependency's dependency back to the main repository from "@envoy//some/path:lib" to "@//some/path:lib" fixed the error in the case where @envoy is the main repository, it does not work in other cases where @envoy is brought in as an external dependency itself. This makes perfect sense given the explanation upthread.

What confuses me is why using --experimental_remap_main_repo in conjunction with deps = ["@envoy//some/path:lib"] doesn't work for me. https://github.com/bazelbuild/bazel/issues/6848 describes nearly the same exact issue I'm facing, yet --experimental_remap_main_repo, which worked there, seems to have no effect in my case.

Is it possible I'm using it wrong? I'm invoking Bazel like:
$ bazel build --experimental_remap_main_repo //some/path/...

and 'bazel version' output indicates I'm running with build label 0.21.0, built Wed Dec 19 12:58:44 2018 (1545224324), which seems like it ought to be recent enough...

Danna Kelmer

unread,
Jan 16, 2019, 3:14:00 PM1/16/19
to Mike Warres, bazel-discuss
The fix for the issue referenced (#6848) did not make it in to the bazel 0.21 release. Could you try building Bazel from head and testing your project again? 

Mike Warres

unread,
Jan 16, 2019, 10:16:05 PM1/16/19
to Danna Kelmer, bazel-discuss
Yup. I can confirm that if I run with a Bazel built from head and --experimental_remap_main_repo specified, the build succeeds (no duplicate symbol link issue). Just for completeness, I also tried with Bazel from head and no --experimental_remap_main_repo, and the build failed as described before. So it does look like the fix works for our case!

I guess the next (and hopefully last) question is when that fix might become available in an official release, and if the plan is for it to eventually become default behavior.
Thanks again for all the help!

Danna Kelmer

unread,
Jan 16, 2019, 11:56:14 PM1/16/19
to Mike Warres, bazel-discuss
Excellent, I'm glad to hear it works!

Regarding your questions, the feature should be in the next Bazel release (0.22) which I believe was already cut around Jan 1st. I don't know if there are any blockers, but if there aren't it should be promoted to release in a week or so.

The feature will become the default behavior, but that will take a while because technically it is a breaking change which needs to follow the incompatible change process. That is a 3 step process:
1. Create a flag called --incompatible_foo that defaults to off in release X (the flag is created and will be in 0.23)
2. Set the default value of the flag to true in release X+1
3. Remove the flag in release X+2

As a side effect of this process, you'll need to use the flag --incompatible_remap_main_repo (as opposed to --experimental_remap_main_repo). Both flags will still exist, but the experimental one will emit a warning. 

Danna
Reply all
Reply to author
Forward
0 new messages