BuckSample icon indicating copy to clipboard operation
BuckSample copied to clipboard

Add dynamic framework to BuckSample

Open qyang-nj opened this issue 7 years ago • 7 comments

This PR unlocks the ability of adding dynamic frameworks which can be shared between bundles (App, Extension or Framework). It works in Buck CLI and generated workspace.

A shared module named SwiftShared is added and imported into ExampleApp and ExampleMessageExtension.

The bare bone of the BUCK rules look like this:

  • The BUCK file for SwiftShared:
apple_library(
    name = "SwiftShared",
    # Setting preferred_linkage to shared makes this a dylib. 
    preferred_linkage = "shared",
    # Set the install_name so consumers of this dylib know where to find it.
    linker_flags = ["-Wl,-install_name,@rpath/%s.framework/%s" % (name, name)],
    configs = { "Debug": {
        # Make this a dylib in Xcode
        "MACH_O_TYPE": "mh_dylib",
    }},
)

apple_bundle(
    name = "SwiftSharedFramework",
    # Specifying the the flavor "shared" makes this a dynamic framework
    binary = ":SwiftShared#shared",
    extension = "framework",
)
  • The BUCK file for the app:
apple_binary(
    name = "ExampleAppBinary",
    # The app binary depends on the library
    deps = [
         ":SwiftShared",
    ],
)

apple_bundle(
    name = "ExampleApp",
    binary = ":ExampleAppBinary",
    extension = "app",
    # the app bundle depends on the framework
    deps = [":SwiftSharedFramework"],
)

A library is a file where binary code lives, while a framework is a directory (bundle) containing a library and some resources. ExampleAppBinary depends on SwiftShared library because the binary needs to know where the actual code is, and the dependency from ExmapleApp bundle to SwiftSharedFramework simply copies the framework to app bundle (ExmapleApp.app). That being said, an extension only needs to depend on the library to compile, not the framework, since the main app bundle is the one who copies over the framework.

Verification

  • binary from Buck CLI build has undefined symbols from SwiftShared image

  • binary from Xcode build has undefined symbols from SwiftShared image

qyang-nj avatar Mar 15 '19 17:03 qyang-nj

I looked more closely to the output and found that the binary of generated project still links to a static library, although the framework is copied in the main app bundle.

I used otool to examine what dylibs are linked to the binary. The binary of Buck CLI build does link to @rpath/SwiftShared.framework/SwiftShared, while the binary of generated project does not.

image (The screenshot of Buck CLI build. The binary of generated project doesn't have the highlighted line. )

Then I looked at the build phases. SwiftShared is built into both static library and framework. 😱 image

qyang-nj avatar Mar 18 '19 00:03 qyang-nj

Great work @qyang-nj and @dfed

bachand avatar Mar 19 '19 06:03 bachand

We made the app binary (//App:ExampleApp) depends on both the SwitfShared and SwfitSharedFramework. As previously stated, depending on SwiftSharedFramework isn't necessary for Buck CLI, but it makes generated project links to the framework. Now, generated project links to both a static library (libSwiftShared.a) and a dynamic framework (SwfitShared.framework). Based on observation, the dynamic linking overrides the static linking, which makes generated project behaves the same way as Buck CLI build.

I am not sure that the overriding is happening properly. I actually think the code is being duplicated.

The only type in SwiftShared is PublicSharedClass. I wanted to verify that this type only appears in the SwiftShared.framework binary and not the main binary.

I first ran nm on the SwiftShared binary to see what the mangled names of these types are.

They look like this in my build:

00000000000018a0 T _$S11SwiftShared06PublicB5ClassC16readFromResourceSSyF
00000000000017e0 T _$S11SwiftShared06PublicB5ClassCACycfC
0000000000001880 T _$S11SwiftShared06PublicB5ClassCACycfc
0000000000001f8c s _$S11SwiftShared06PublicB5ClassCMF
0000000000002210 b _$S11SwiftShared06PublicB5ClassCML
0000000000001830 T _$S11SwiftShared06PublicB5ClassCMa
00000000000021a0 d _$S11SwiftShared06PublicB5ClassCMf
0000000000002178 D _$S11SwiftShared06PublicB5ClassCMm
0000000000001f44 S _$S11SwiftShared06PublicB5ClassCMn
00000000000021b0 D _$S11SwiftShared06PublicB5ClassCN
0000000000001d60 T _$S11SwiftShared06PublicB5ClassCfD
0000000000001d40 T _$S11SwiftShared06PublicB5ClassCfd
0000000000001f1c s _$S11SwiftSharedMXM

I then grep'ed for those names in the ExampleApp binary, and they appear. Screen Shot 2019-03-19 at 6 04 48 PM

This indicates that the code is being duplicated, which would make sense since we still see the main app linking to libSwiftShared.a in Xcode.

Screen Shot 2019-03-19 at 6 05 42 PM

bachand avatar Mar 20 '19 01:03 bachand

Update: I was mistaken about this. See @qyang-nj's comment below.


~~It seems like these symbols are being duplicated in a Buck CLI build as well.~~

~~I built the project with make build.~~

~~ExampleApp binary~~ ~~SwiftShared binary~~
Screen Shot 2019-03-19 at 7 13 03 PM Screen Shot 2019-03-19 at 7 11 35 PM

bachand avatar Mar 20 '19 02:03 bachand

Buck CLI build is expected. The U means undefined, which will be linked at runtime.

qyang-nj avatar Mar 20 '19 02:03 qyang-nj

@qyang-nj and I learned that https://github.com/airbnb/BuckSample/pull/53 caused the code duplication described in https://github.com/airbnb/BuckSample/pull/52#issuecomment-474643614

bachand avatar Mar 20 '19 04:03 bachand

@bachand This PR should be working for both Buck CLI and generated Xcode project. I'd like you to take a closer look and validate it.

qyang-nj avatar May 21 '19 05:05 qyang-nj

Looking through open PRs where I'm tagged: should we close this?

dfed avatar Oct 11 '22 13:10 dfed