bazel-ios/rules_ios

ld: duplicate symbols when swift_library depend on apple_framework

i-pavlov opened this issue · 4 comments

Hi, I was trying to use static apple_framework to compile a mixed ObjC+swift module.

The apple_framework is not a leaf node of my dependency graph, so it has some swift_library dependencies that are also used by other pure-swift modules in my app.

As a result, symbols of these dependencies are duplicated in top-level binary, so I'm getting a linker issue. It looks ~ similar to problem discussed in #430. But I still able to reproduce in the latest commit from master branch.

Do you have any suggestions about this scenario? Is it supported by design?
I guess I can update all modules in the app to be dynamic frameworks, but it looks like the last possible option :D

Sample repo

I created a repo with the smallest possible way to reproduce: https://github.com/i-pavlov/reproduce-rules-ios-duplicate-symbols

The dependency graph is pretty simple. It has three modules:

  1. Data (swift_library)
  2. Utils (apple_framework, static)
  3. Module (swift_library)
  4. ModuleTests (unit-test bundle)
1. `Data` has no dependencies
2. `Utils` depends on `Data`
3. `Module` depends on `Data` and `Utils`
4. `ModuleTests` depends on `Module`

If I remove the dependency between Module and Data the issue will disappear.

Steps to reproduce

Need to build unit test bundle

bazel build //App/Module:ModuleTests

Linker error

When I compile unit-tests, I'm getting this error.
Looks like two versions of libData were created. When we try to link them - getting an error.

 /Users/ipavlov/Developer/bazel_platforms_test/App/Module/BUILD:24:14: Linking App/Module/ModuleTests.__internal__.__test_bundle_bin failed: (Aborted): wrapped_clang failed: error executing command 
  (cd /private/var/tmp/_bazel_ipavlov/23ebc518c1b7878a1b2c6f2ef4486ae1/sandbox/darwin-sandbox/332/execroot/__main__ && \
  exec env - \
    APPLE_SDK_PLATFORM=iPhoneSimulator \
    APPLE_SDK_VERSION_OVERRIDE=15.2 \
    PATH=/bin:/usr/bin:/usr/local/bin \
    XCODE_VERSION_OVERRIDE=13.2.1.13C100 \
    ZERO_AR_DATE=1 \
  external/local_config_cc/wrapped_clang @bazel-out/ios-sim_arm64-min14.2-applebin_ios-ios_sim_arm64-dbg-ST-19517cdb79de/bin/App/Module/ModuleTests.__internal__.__test_bundle_bin-2.params)
# Configuration: 59b07bb4057e96974d443e7f597806a5e3cd105462e50da71b86a983ab7c4496
# Execution platform: @local_config_platform//:host

Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging
duplicate symbol 'nominal type descriptor for Data.Item' in:
    bazel-out/ios-sim_arm64-min14.2-applebin_ios-ios_sim_arm64-dbg-ST-19517cdb79de/bin/App/Data/libData.a(Contract.swift.o)
    bazel-out/ios-sim_arm64-min14.2-applebin_ios-ios_sim_arm64-dbg-ST-37412340fa95/bin/App/Data/libData.a(Contract.swift.o)

Subcommands

It's clear that libData compiled twice if print subcommands:

The first one:

SUBCOMMAND: # //App/Data:Data [action 'Compiling Swift module //App/Data:Data', configuration: 92d4594cb8fd2799aa91d544c582450cb49f7bc02075d927c8db7c90cf3652a7, execution platform: @local_config_platform//:host]
(cd /private/var/tmp/_bazel_ipavlov/23ebc518c1b7878a1b2c6f2ef4486ae1/execroot/__main__ && \
  exec env - \
    APPLE_SDK_PLATFORM=iPhoneSimulator \
    APPLE_SDK_VERSION_OVERRIDE=15.2 \
    SWIFT_AVOID_WARNING_USING_OLD_DRIVER=1 \
    XCODE_VERSION_OVERRIDE=13.2.1.13C100 \
  bazel-out/darwin_arm64-opt-exec-2B5CBBC6-ST-f0056b8e5833/bin/external/build_bazel_rules_swift/tools/worker/worker swiftc @bazel-out/ios-sim_arm64-min14.2-applebin_ios-ios_sim_arm64-dbg-ST-37412340fa95/bin/App/Data/Data.swiftmodule-0.params)
# Configuration: 92d4594cb8fd2799aa91d544c582450cb49f7bc02075d927c8db7c90cf3652a7
# Execution platform: @local_config_platform//:host

The second one:

SUBCOMMAND: # //App/Data:Data [action 'Compiling Swift module //App/Data:Data', configuration: 03f1ac5615d5fcff438b70835b28294822a3e2d8e6fd637bb3830da04dd4addb, execution platform: @local_config_platform//:host]
(cd /private/var/tmp/_bazel_ipavlov/23ebc518c1b7878a1b2c6f2ef4486ae1/execroot/__main__ && \
  exec env - \
    APPLE_SDK_PLATFORM=iPhoneSimulator \
    APPLE_SDK_VERSION_OVERRIDE=15.2 \
    SWIFT_AVOID_WARNING_USING_OLD_DRIVER=1 \
    XCODE_VERSION_OVERRIDE=13.2.1.13C100 \
  bazel-out/darwin_arm64-opt-exec-2B5CBBC6-ST-dedf359489b7/bin/external/build_bazel_rules_swift/tools/worker/worker swiftc @bazel-out/ios-sim_arm64-min14.2-applebin_ios-ios_sim_arm64-dbg-ST-19517cdb79de/bin/App/Data/Data.swiftmodule-0.params)
# Configuration: 03f1ac5615d5fcff438b70835b28294822a3e2d8e6fd637bb3830da04dd4addb
# Execution platform: @local_config_platform//:host

This is expected when linking static frameworks. Since Module depends on Data and Util does as well, Data needs to be a dynamic framework or you'll get duplicate symbols.

Thanks for the explanation!
Btw, looks like I found ~ a workaround for my case.

If I convert Data to a static framework too, the issue will not happen.
Looks like the linking strategy for swift_library and static apple_framework are slightly different.

This is expected when linking static frameworks. Since Module depends on Data and Util does as well, Data needs to be a dynamic framework or you'll get duplicate symbols.

I think there's a few wires crossed in the build file - you should be able to have many apps, tests, and apple_frameworks depending on one another - and not have a duplicate symbol:

  1. adding Module through swift_library might cause an issue
  2. compiling the file Test.swift into 2 times

Looks like the linking strategy for swift_library and static apple_framework are slightly different.

I think the issue might be in the way the build graph is setup: running these deps through swift_library here might not be accounted for

After some time debugging and diving into bazel I found the root cause of my problem.

The default value of --ios_multi_cpus is x86_64, but I'm compiling it on an arm64 laptop.
So setting --ios_multi_cpus=sim_arm64 made my tests compilable.

Still not sure how ios_multi_cpus can cause the Data module to be compiled twice by using different configurations. I think it might be related to the 1:K transition for framework packaging.

But if initially it is caused by incorrect build parameters, I don't think someone else can get the same problem in real-life scenarios.