Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incompatible_remove_legacy_whole_archive: Remove --legacy_whole_archive while setting default to false #7362

Closed
hlopko opened this issue Feb 6, 2019 · 20 comments
Assignees
Labels
incompatible-change Incompatible/breaking change P1 I'll work on this now. (Assignee required) team-Rules-CPP Issues for C++ rules

Comments

@hlopko
Copy link
Member

hlopko commented Feb 6, 2019

Flag: --incompatible_remove_legacy_whole_archive
Available since: 0.25
Will be flipped: 0.26, unless we hear from Bazel users it's not enough. Bazel 1.0 is the latest flipping possibility, but we will gladly postpone the flip until then, if Bazel users need more time. Please speak up.

Motivation

The docs of this flag say:

--[no]legacy_whole_archive default: "true"
When on, use --whole-archive for cc_binary rules that have linkshared=1 and either linkstatic=1 or '-static' in linkopts. This is for backwards compatibility only. A better alternative is to use alwayslink=1 where required.

What this means is that all library archives (and library object groups, as defined by --start-lib/--end-lib) are added to the link command wrapped in -whole-archive, -no-whole-archive, which instructs the linker to always include the entire library. Even with dead code elimination, this can be pernicious if the library defines some global initializer, as that will cause all of the code dependencies of those initializers to be kept.

History

--(legacy_)whole-archive was introduced on 2004-04-19, with the commit description saying:

I think --whole-archive was just the
magical incantation that got swigdeps.so working.
...
You're right though that the real question is what would break if we
removed this, and I don't really have a good idea.

Problem

"Legacy" whole-archive behavior is a poor default, but it is difficult to change.

--[no]legacy_whole_archive is a whole build option. It is impossible to "tag" individual shared, statically linked, dynamic libraries which can be built without "whole-archive" semantics.

Care must be taken to flip the default--libraries which export symbols must be tagged as alwayslink=1 unless some other mechanism exists in the dylib to preserve the symbols which are being exported.

It is a difficult thing to test: unexported symbols are only noticed at runtime, which means that 100% test coverage would have to exist for a depot-wide mass cleanup.

Migration

--legacy_whole_archive will be superseded by --incompatible_remove_legacy_whole_archive. --incompatible_remove_legacy_whole_archive, when true, removes both the old flag, and the behavior enabled by features (explained below).

Bazel now has a mechanism to configure individual cc_binary targets which can be built without --legacy_whole_archive. This mechanism still honors the --incompatible_remove_legacy_whole_archive. This mechanism is based on feature named "legacy_whole_archive". When added to the features attribute of cc_binary, Bazel will keep the legacy whole archive behavior, but only for the cc_binary, not for the whole build.

This is the migration plan we will follow at Google:

  1. Add --features=legacy_whole_archive to the global .bazelrc.
  2. Users can start adding features = "-legacy_whole_archive" (note, minus here) to targets that are safe to build without the legacy behavior.
  3. Add a presubmit check telling users they should add features = [ "-legacy_whole_archive" ].
  4. Have a presubmit check comparing the binary size with and without the feature, to motivate users to migrate.
  5. Once everything is migrated, remove legacy_whole_archive from global .bazelrc.
  6. Celebrate
  7. Remove -legacy_whole_archive features from targets.
@hlopko hlopko added team-Rules-CPP Issues for C++ rules incompatible-change Incompatible/breaking change bazel 1.0 labels Feb 6, 2019
@hlopko hlopko self-assigned this Feb 6, 2019
@hlopko hlopko added the P1 I'll work on this now. (Assignee required) label Feb 6, 2019
@hlopko
Copy link
Member Author

hlopko commented Feb 6, 2019

CC @trybka

bazel-io pushed a commit that referenced this issue Feb 13, 2019
This flag disables legacy whole archive behavior, as well as makes the --legacy_whole_archive option a noop. See details in
#7362.

It also introduces a possibility to enable legacy behavior per target by `--features=legacy_whole_archive` option, or `features = [ "legacy_whole_archive" ]` attribute.

RELNOTES: None.
PiperOrigin-RevId: 233691404
bazel-io pushed a commit that referenced this issue Feb 18, 2019
*** Reason for rollback ***

Breaks a target in the canary's nightly, thus preventing us from releasing tomorrow:

[]

b/124656723

*** Original change description ***

Introduce --incompatible_remove_legacy_whole_archive

This flag disables legacy whole archive behavior, as well as makes the --legacy_whole_archive option a noop. See details in
#7362.

It also introduces a possibility to enable legacy behavior per target by `--features=legacy_whole_archive` option, or `features = [ "legacy_whole_archive" ]` attribute.

RELNOTES: None.
PiperOrigin-RevId: 234481950
@dkelmer
Copy link
Contributor

dkelmer commented Apr 3, 2019

@hlopko I added the breaking-change-0.26 label since no one seems to have spoken up about increasing the window. TBH, I'm not sure what the significance of the label is if you don't actually flip the flag, but letting you know jic :)

@hlopko
Copy link
Member Author

hlopko commented Apr 5, 2019

@dkelmer Thank you! Because the flag is only available in 0.25, people actually didn't have a chance to try it out yet. I'm not in hurry to flip this flag, so I added migration-0.26 and breaking-change-0.27.

@dkelmer
Copy link
Contributor

dkelmer commented Apr 5, 2019

Sounds good :)

@pmuetschard
Copy link
Contributor

I think this breaks Android. On Android, to build an .so, one has to create a cc_library rule and depend on it from the android_binary target. This will cause bazel to create an .a for the cc_library and then attempt to create the .so from it. Without wrapping the .a in --whole-archive, well, no symbols make it into the .so.

Example commands I'm seeing:

clang ... -fPIC -c foo.cpp -o foo.o
ar rcsD ...libfoo.a ...foo.o
clang -shared -o ..._nativedeps/...so ...libfoo.a

Adding features = ["legacy_whole_archive"] to the cc_library or the android_binary rule had no effect.

@hlopko
Copy link
Member Author

hlopko commented May 13, 2019

Did you try adding alwayslink = 1 to cc libraries symbols of which you need in the final .so?

@pmuetschard
Copy link
Contributor

Adding alwayslink = 1 did indeed bring back the --whole-archive flag for the Android libs.

@laurentlb
Copy link
Contributor

This flag was not flipped in 0.27, targeting 1.0?

@hlopko
Copy link
Member Author

hlopko commented Jun 3, 2019

Yes, this didn't make it into 0.27, we're postponing the flip until 1.0. Could you create the labels so I can update our flags?

@ghost
Copy link

ghost commented Oct 30, 2019

I know I'm very late to the game here, but what's the suggested mitigation? I had been using cc_binary with linkshared and linkstatic in a few places to force a build of a .so which contains all symbols of all dependencies so it could be shipped on its own. I only care about linking in these symbols in the few cases where I want to ship a dependency-free .so, so I definitely don't want to pollute all of my other cc_library rules with alwayslink = True just to support this fairly rare use case?

@hlopko
Copy link
Member Author

hlopko commented Oct 31, 2019

Hey @johnmarkwayve, What you could do is to introduce a cc_library wrapper that you use throughout your project. In the wrapper you'll set alwayslink attribute to a select (https://docs.bazel.build/versions/master/configurable-attributes.html). The default condition will be 0, the condition with a build_setting or --define will be 1. Whenever you'll be building the .so for distribution, you will pass that build_setting or --define.

This will roughly emulate the behavior of --legacy_whole_archive. Would that work for you?

@hlopko
Copy link
Member Author

hlopko commented Oct 31, 2019

Oh I was just told that alwayslink attribute is not selectable. That complicates things. then your cc_library wrapper will have to create 2 cc_libraries, one with alwayslink set, one with it unset, and an alias rule with the select in its 'actual' attribute. Select will choose one of cc_libraries.

@ghost
Copy link

ghost commented Oct 31, 2019

@hlopko thanks for the advice. It's a lot uglier than I'd like but it should work.

I realise my use case isn't super common, but it doesn't seem unreasonable either ("this .so target exists to be shipped standalone without dependencies, but the rest of the codebase wants to behave normally"). It seems odd to me that alwayslink has been put onto the depended-on targets rather than the final target? Why should my cc_library care whether you decide to link in the whole archive or not (meanwhile, the downstream target might be quite opinionated about how linking happens)?

@trybka
Copy link
Contributor

trybka commented Nov 1, 2019

@johnmarkwayve yeah, this is actually just a deficiency of how cc_library represents dependencies.

alwayslink serves other purposes (e.g. if you have module initializers that register something and no actual ODR-use of any symbol in the library to tell the linker to keep this library).

However --legacy_whole_archive was a different kind of hammer that forced everything to be linked into a shared library .so.

In actuality, alwayslink is more of a property of a dependency edge rather than a dependency itself.

One could imagine having an alwayslink_deps attribute that could accomplish this.

In lieu of such a thing, alwayslink with some alias()/select() magic should work.
Note, you only need alwayslink on libraries which define entry-point symbols. The other dependencies should be kept by the linker properly.

The alternatives are less palatable (IMO), but:
You can pass --undefined SYMBOL for each symbol in linkopts, and that will cause the linker to keep those symbols as well.
You can write out an assembly file that refers to the symbols in .rodata somehow, but that's likely even less maintainable.

@jwnimmer-tri
Copy link
Contributor

I have the exact problem as @johnmarkwayve.

I have hundreds of small targets cc_library(linkstatic = 1) that are used both (1) by C++ applications with a main() routine (where we definitely do not want alwayslink = True for bloat reasons), and (2) by Python as a loaded module (as a single, large shared library composed of the small cc_library targets rolled into one).

Because the alwayslink is at the point of declaration instead of point of use (aka not on the edges), I can't tag my small cc_library targets with it. But there seems to be no built-in way to roll up cc_library targets into a whole-archive shared library, which for my use case is a major regression and loss of capability.

It seems like at minimum an attribute like the alwayslink_deps proposed above should exist, so that I can write ...

cc_library(
  name = "all.a",
  alwayslink = True,
  alwayslink_deps = [":small1", ":small2", ":small3"],
)

... at which point I could convert all.a into a cc_binary(all.so) shared library by writing one more simple rule.

The work-around of using a macro to declare two cc_library targets (one with and without alwayslink) is a non-starter. For one thing, it compiles all of the sources twice (even if I mark them as "manual" and only induce builds on the flavors that are actually used). For another, it gets us into a "red object" vs "blue object" world, where the deps lines need to be respelled inside the macro to have the correct color depending on whether the ultimate cc_binary is a program or a shared library.

For now my workaround is to implement a cc_whole_archive_library rule (see RobotLocomotion/drake#12262) that repacks all objects into a single static archive, which I can then label with alwayslink = True. This is a bit gross from a scale perspective (build footprint and I/O) but appears to get the job done.

But in general I'd like if cc_library and/or cc_binary had the capability to deliver shared objects out of the box, instead of having to roll my own starlark for it.

timokau pushed a commit to timokau/tensorflow that referenced this issue Nov 2, 2019
…ge: bazelbuild/bazel#7362

Imported from GitHub PR tensorflow#32040

Update tensorflow for upcoming incompatible change: bazelbuild/bazel#7362

This PR updates `cc_library`s to use `alwayslink=1` when needed. Currently, library archives and library object groups are wrapped in `--whole_archive`, `--no_whole_archive`, thus the whole library is always linked. This will change with Bazel 1.0, and libraries that export otherwise unused symbols must be tagged as `alwayslink=1`.

Copybara import of the project:

  - 001117f Add alwayslink = 1 to cc_libraries to ensure that their s... by scentini <[email protected]>
  - 846b892 Fix a typo by scentini <[email protected]>
  - ceb6ae5 Merge 846b892 into 3c2be... by scentini <[email protected]>

PiperOrigin-RevId: 267108524
(cherry picked from commit c3619ce)
timokau pushed a commit to timokau/tensorflow that referenced this issue Nov 2, 2019
bazelbuild/bazel#7362

Barring last minute regressions, this is the last commit of this type.

This PR updates `cc_library`s to use `alwayslink=1` when needed. Currently, library archives and library object groups are wrapped in `--whole_archive`, `--no_whole_archive`, thus the whole library is always linked. This changes with Bazel 1.0, and libraries that export otherwise unused symbols must be tagged as `alwayslink=1`.

PiperOrigin-RevId: 277303614
Change-Id: I5b2aefbb9d402bfd1c21f4d2ad95592ba3a392d5
(cherry picked from commit 9760bc2)
timokau pushed a commit to timokau/tensorflow that referenced this issue Nov 2, 2019
bazelbuild/bazel#7362

This PR updates `cc_library`s to use `alwayslink=1` when needed. Currently, library archives and library object groups are wrapped in `--whole_archive`, `--no_whole_archive`, thus the whole library is always linked. This changes with Bazel 1.0, and libraries that export otherwise unused symbols must be tagged as `alwayslink=1`.

This PR fixes tensorflow#32835 (once again, as it was fixed by tensorflow#33415 but the codebase regressed in the meantime.)
It also fixes most of the TF tests.

PiperOrigin-RevId: 276772147
Change-Id: I05db02e84200a484df52f4f02b601dc486636912
(cherry picked from commit 52167ad)
@ghost
Copy link

ghost commented Nov 4, 2019

To add a question for discussion:

What exactly is "cc_library" supposed to encapsulate or represent?

  1. An actual collection of built artefacts for the given platform?
  2. The packaging of source code and dependencies?
  3. The provision of some high-level functionality?
  4. Something else?

I would argue that having always_link being an upstream parameter (as it is at the moment), forces you into something like (1). While with (2) or (3) point towards at least having the option of specifying dependencies as whole-archive (it's the downstream target that gets to decide how it uses its dependencies).

I agree with the original point of this issue, that whole-archive shouldn't be default, and I'm not against having always_link upstream as a hint ("it doesn't make much sense to use this library without linking the lot"). But as @trybka says, really it's a feature of the dependency edge, which is defined by the downstream target, not the upstream cc_library.

@hlopko
Copy link
Member Author

hlopko commented Nov 5, 2019

One random idea that I didn't pursue is that maybe we can produce a linker script (version script) that will export all symbols present? I haven't written a linker script but it sounds like it might be possible? Passing the linker script to the cc_binary producing shared library is simple.

tensorflow-copybara pushed a commit to tensorflow/tensorflow that referenced this issue Nov 11, 2019
…default of --incompatible_remove_legacy_whole_archive in cl/277339372

This should fix the latest issue reported in #33415.
Also fixes an internally reported missing symbol.

Related to bazelbuild/bazel#7362

PiperOrigin-RevId: 279792794
Change-Id: I6f5d26ee37b9c886662df5e2daf9273c15cae865
timokau pushed a commit to timokau/tensorflow that referenced this issue Nov 12, 2019
…rty/protobuf:protobuf to include alwayslink = 1.

This is needed because tensorflow_framework.so currently defines the symbols provided by //third_party/protobuf:protobuf. Incompatible change bazelbuild/bazel#7362 is going to be flipped in Bazel 1.0. and is going to disable --legacy_whole_archive that was enabled by default and that caused every transitive cc_library linked into a transitive shared library to be linked in a whole-archive block. Putting alwayslink = 1 recreates the behavior from before the flag flip.

The principled solution is to talk to the protobuf team and ask them to maintain a cc_library with alwayslink = 1 target that projects such as TF can depend on. This PR is a fast-fix for Bazel 1.0. And maybe we'll discover that those symbols shouldn't be defined by the protobuf_tensorflow.so at all and instead //third_party/protobuf:protobuf should be a dependency of executables using those symbols.

PiperOrigin-RevId: 266779761
(cherry picked from commit 2d2d6cf)
timokau pushed a commit to timokau/tensorflow that referenced this issue Nov 12, 2019
…-incompatible_remove_legacy_whole_archive (bazelbuild/bazel#7362) is flipped

Currently, library archives and library object groups are wrapped in `--whole_archive`, `--no_whole_archive`, thus the whole library is always linked. This will change with Bazel 1.0, and libraries that export otherwise unused symbols must be tagged as `alwayslink=1`.

PiperOrigin-RevId: 267115768
(cherry picked from commit afb7639)
tensorflow-copybara pushed a commit to tensorflow/tensorflow that referenced this issue Nov 13, 2019
…ymbols

Imported from GitHub PR #34044

This should fix tensorflow/addons#663, caused by changing logic in Bazel on which symbols we export (see bazelbuild/bazel#7362).
Copybara import of the project:

PiperOrigin-RevId: 280275294
Change-Id: I1a21010f47367f66ab33ba51aefe5226f8191b30
karimnosseir pushed a commit to karimnosseir/tensorflow that referenced this issue Nov 13, 2019
…default of --incompatible_remove_legacy_whole_archive in cl/277339372

This should fix the latest issue reported in tensorflow#33415.
Also fixes an internally reported missing symbol.

Related to bazelbuild/bazel#7362

PiperOrigin-RevId: 279792794
Change-Id: I6f5d26ee37b9c886662df5e2daf9273c15cae865
@jwnimmer-tri
Copy link
Contributor

Update: in RobotLocomotion/drake#12262, I've written a cc_whole_archive_library(deps = ...) which is basically cc_library(alwayslink_deps = ...). It just rewrites libraries_to_link to add alwayslink = True to every edge, instead of forcing it to appear upstream. Anyone is welcome to reuse it (Apache-2.0) for yourselves if it helps.

tensorflow-copybara pushed a commit to tensorflow/tensorflow that referenced this issue Nov 23, 2019
…for TensorFlow.

Context: swiftlang/swift#28438 (comment)

Related Bazel change: bazelbuild/bazel#7362
(`--incompatible_remove_legacy_whole_archive`)

Previously, libraries were wrapped in `--whole_archive` and `--no_whole_archive`
so that entire libraries were linked. With Bazel 1.0, libraries that export
unused symbols must be explicitly tagged with `alwayslink = 1`.

PiperOrigin-RevId: 282157053
Change-Id: Ica85797aeeb99e15402cdd06e61bbbf58011f141
frc971-automation pushed a commit to frc971/971-Robot-Code that referenced this issue Nov 9, 2020
When upgrading to the latest bazel, I kept seeing errors like this:

  File "/dev/shm/bazel-sandbox.62240ff599163761312fdbd652f7452abc23edc253dee6214f406f29dde5568b/linux-sandbox/2481/execroot/org_frc971/bazel-out/host/bin/y2019/control_loops/python/intake.runfiles/slycot_repo/slycot/_wrapper.py", line 1, in <module>
      from slycot._fortranwrapper import *
  ImportError: dynamic module does not define init function (init_fortranwrapper)

And sure enough:

  $ objdump -TC bazel-bin/external/slycot_repo/slycot/_fortranwrapper.so

  bazel-bin/external/slycot_repo/slycot/_fortranwrapper.so:     file format elf64-x86-64

  DYNAMIC SYMBOL TABLE:
  0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
  0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
  0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
  0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
  0000000000002009 g    D  .fini_array    0000000000000000  Base        _end
  0000000000002008 g    D  .fini_array    0000000000000000  Base        _edata
  0000000000002008 g    D  .fini_array    0000000000000000  Base        __bss_start

With version 1.0, bazel changed the default linker flags. See
bazelbuild/bazel#7362 for some more
information.

This patch forces the symbols defined in the `cc_library` to make it
into the .so file.

Change-Id: Id71f57001ae84ee37d5951eb54261c0774320ff4
jessecantu referenced this issue in jessecantu/jamstack-cfp Oct 3, 2021
luca-digrazia pushed a commit to luca-digrazia/DatasetCommitsDiffSearch that referenced this issue Sep 4, 2022
    Fixes #7362

    RELNOTES: --incompatible_remove_legacy_whole_archive has been flipped. See bazelbuild/bazel#7362 for more information
    PiperOrigin-RevId: 267126102
luca-digrazia pushed a commit to luca-digrazia/DatasetCommitsDiffSearch that referenced this issue Sep 4, 2022
    *** Reason for rollback ***

    Breaks a target in the canary's nightly, thus preventing us from releasing tomorrow:

    []

    b/124656723

    *** Original change description ***

    Introduce --incompatible_remove_legacy_whole_archive

    This flag disables legacy whole archive behavior, as well as makes the --legacy_whole_archive option a noop. See details in
    bazelbuild/bazel#7362.

    It also introduces a possibility to enable legacy behavior per target by `--features=legacy_whole_archive` option, or `features = [ "legacy_whole_archive" ]` attribute.

    RELNOTES: None.
    PiperOrigin-RevId: 234481950
luca-digrazia pushed a commit to luca-digrazia/DatasetCommitsDiffSearch that referenced this issue Sep 4, 2022
    This flag disables legacy whole archive behavior, as well as makes the --legacy_whole_archive option a noop. See details in
    bazelbuild/bazel#7362.

    It also introduces a possibility to enable legacy behavior per target by `--features=legacy_whole_archive` option, or `features = [ "legacy_whole_archive" ]` attribute.

    RELNOTES: None.
    PiperOrigin-RevId: 233691404
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
incompatible-change Incompatible/breaking change P1 I'll work on this now. (Assignee required) team-Rules-CPP Issues for C++ rules
Projects
None yet
Development

No branches or pull requests

8 participants