buck
buck copied to clipboard
Swift support
Since all the xcode problems are not limited to Objective-C 😆 My question is if there are any plans to support Swift.
We don't currently have any plans around this. We'd happily help someone get it in, but we don't have a need for it internally yet.
I did some investigation into the ins and outs of using the swift and clang command-line to compile binaries which use Swift and Objective-C together. Here's what I found:
https://www.facebook.com/beng/posts/10153662256352720
Example:
https://gist.github.com/bhamiltoncx/3d846862eb42eede17fa
I hacked together very very basic Swift support for Xcode project generation:
https://github.com/facebook/buck/tree/swift
I updated the demo app to use it:
https://github.com/fbsamples/bucksamples/tree/swift
This doesn't support Swift frameworks yet, but the basics work.
I'd love to see this too. Is anyone actively working on it? Can I be of help?
I don't believe anybody is actively working on it, so if you wanted to try and pick up @bhamiltoncx's work and drive it forward, I'm pretty sure that would be okay.
If you are interested in continuing that work, @Coneko has a good write up of how Buck uses graphs, which is a good primer for one of our core pieces of architecture: https://www.facebook.com/notes/uri-baghin/architecture-of-buck-1-graphs/1660525917559134
Sorry for the long silence here. Expect to see some movement soon!
So the very basics have landed. You can build apple_binary()
rules and apple_bundle()
rules with one or more swift_library()
dependencies. OS X, iOS simulator, and iOS device are all working.
swift_library()
rules currently cannot have any dependencies, and there isn't any Obj-C/Swift interop. But it's a start.
How can I specify a particular swift version? I've tried to use *_toolchains_override
but with no luck :-( Buck chooses the latest toolchain but my app is not ready for Swift 3.0.
You're right, right now we can only override the toolchain globally. @nguyentruongtho is working on adding swift-specific toolchain override as a config option.
In the mean time, you could use this settings:
[apple]
iphonesimulator10.0_toolchains_override = com.apple.dt.toolchain.Swift_2_3,com.apple.dt.toolchain.XcodeDefault
iphoneos10.0_toolchains_override = ...
@nguyentruongtho thank you! It worked!
I continued playing with Buck and Swift and faced some strange behaviour while running Swift tests only. Objective-C tests are working fine! And even a mix of Objective-C and Swift is working fine.
I've created a small example that illustrates the issues here: https://github.com/fkorotkov/buck-sandbox.
Even added Travis so you can easily see output! 😜 https://travis-ci.org/fkorotkov/buck-sandbox/builds/162268899
yeah, apple_test
can handle mixed swift & objective-c (thanks to this PR: https://github.com/facebook/buck/pull/881). For swift-only target, I've just landed https://github.com/facebook/buck/pull/895, that PR should make apple_test
to work with swift-only target, but obviously I have not tested it yet, you can try.
@nguyentruongtho I would love to but brew install --HEAD buck
is failing. Fixed it here: https://github.com/facebook/buck/pull/898
@nguyentruongtho I built Buck locally with the fix and only swift tests are working now!
Very nice, I didn't think of this when working on https://github.com/facebook/buck/pull/895. Good to know :rocket:!
BTW I'm working on a fix for https://github.com/facebook/buck/issues/909.
I doesn't look like an issue to me, you should not include a header from parent folder like in objc/Foo.h
, but I might be wrong, @ryu2 probably knows more.
Agreed on a bad example structure. Fixed the example :-) see https://github.com/facebook/buck/issues/909#issuecomment-250474596
I have a library that has Swift and Objective-C code. Some Swift classes I want to export to Objective-C, I see that buck correctly generates MyLibrary-Swift.h
for all classes marked as @objc
, however I don't see a way export this header from my library, so that I can used it in my dependencies. Is this possible?
apple_library(
name = 'MyLibrary',
srcs = [
'ObjC.m',
'SwiftKit1.swift',
'SwiftKit2.swift'
],
exported_headers = [
'ObjC.h',
'MyLibrary-Swift.h' <---------------------------------------- this doesn't work
],
visibility = ['PUBLIC']
)
Currently, swift_library
can export its generated header, but not for an apple_library
that contains swift files, feel free to open a ticket for it.
Your example is actually a special case of dependency problem, where a swift_library
cannot have an apple_library
target as a dependency. I'm currently working to fix this.
Created issue here: #943
If you are working on it, do you have a branch somewhere for me to have a look? I want my team to migrate to Buck, but we may have to implement few things, that seem to be missing right now, for example support for clang module maps, and better swift support.
hey folks, how is this going ?
while the swift support is working, it is currently rebuilding all the swift files if only one of them changes
For instance, here is my BUCK
file using for the demo app modified to use some SwiftProtobuf
generated models:
apple_resource(
name = 'BuckDemoAppResources',
files = glob(['*.png']),
dirs = [],
)
apple_bundle(
name = 'BuckDemoApp',
binary = ':BuckDemoAppBinary',
extension = 'app',
info_plist = 'Info.plist',
info_plist_substitutions = {
'PRODUCT_BUNDLE_IDENTIFIER': 'bucktest',
'CURRENT_PROJECT_VERSION': '1',
},
)
apple_binary(
name = 'BuckDemoAppBinary',
deps = [':BuckDemoAppResources', ":SwiftProtobuf"],
srcs = glob([
'*.swift',
'models/*.swift',
]),
frameworks = [
'$SDKROOT/System/Library/Frameworks/UIKit.framework',
'$SDKROOT/System/Library/Frameworks/Foundation.framework',
':SwiftProtobuf',
],
)
apple_package(
name = 'BuckDemoAppPackage',
bundle = ':BuckDemoApp',
)
prebuilt_apple_framework(
name = 'SwiftProtobuf',
framework = 'Carthage/Build/iOS/SwiftProtobuf.framework',
)
The output:
[-] PROCESSING BUCK FILES...FINISHED 0.2s [100%] 🐳 New buck daemon
[+] DOWNLOADING... (0.00 B/S, TOTAL: 0.00 B, 0 Artifacts)
[+] BUILDING...20.8s [27%] (3/11 JOBS, 0 UPDATED, 0 [0.0%] CACHE MISS)
|=> IDLE
|=> //bucktest/bucktest:BuckDemoAppBinary#dwarf-and-dsym,iphonesimulator-x86_64,strip-non-global,swift-compile... 2)
|=> IDLE
|=> IDLE
[-] BUILDING...FINISHED 48.7s [100%] (11/11 JOBS, 8 UPDATED, 8 [72.7%] CACHE MISS)
[-] INSTALLING...FINISHED 5.5s
@steeve I don't believe it's possible for Swift to recompile individual files at this time: https://github.com/apple/swift/blob/master/docs/Driver.md
@steeve The solution for now is to split your big target into smaller, independent ones.
@nguyentruongtho yes, I'm using another PR that does exactly this: create a target per file
works well
I created a PR a while ago https://github.com/facebook/buck/pull/917 for this split, but observed a minor improvement. Can you link me to your PR?
@nguyentruongtho Hi Tho, I found that you have many experiences on the Buck for Swift, I tried to build a dynamic framework by Buck, anything seems ok until the compiler cannot compile a file that is implemented the extension
for a protocol.
Do you know what is the tool Buck use to build the files of Swift, does it use xcodebuild?
This is the BUCK file I use, the code compiled well on xctool, xcodebuild or xcode:
apple_binary(
name = 'build',
srcs = glob(['*/*.swift']),
link_style = 'shared',
frameworks = [
'$SDKROOT/System/Library/Frameworks/Foundation.framework',
'$SDKROOT/System/Library/Frameworks/UIKit.framework',
'$SDKROOT/System/Library/Frameworks/PlaygroundSupport.framework',
':XCGLogger',
':BrightFutures',
':Result',
':ATRestKit',
':Alamofire',
':Reachability',
':KZPropertyMapper',
':ATUtils',
':RealmSwift',
':Realm.Private',
':Realm',
]
)
The error code said I do extend
an undeclared class. It make me thing the compiler try compile file per file, not a whole framework.
@huy-le Framework doesn't work yet for swift as I commented in https://github.com/facebook/buck/issues/1237#issuecomment-286421087.
Buck doesn't use xcodebuild, it uses command line tools (https://developer.apple.com/download/more/).
What's the status of Swift support? Is it implemented, but undocumented? Where can I find documentation (or the equivalent)?
When I try to compile an apple_binary with a main.swift, I am informed that the CXX platform doesn't contain a swift compiler, so I suppose I am in a C/C++/Obj-C context.
Uber engineers got it working on their 500KLoc codebase so it should be good. But it wasn't upstreamed yet. Hopefully there will be more info about it in #1302 soon.
Sent from mobile
Ah, so the Uber engineers added Swift support themselves?
@vhbit do you know if the uber fork is available anywhere? Or is that still yet to be open sourced?
@alanzeino, can you comment on this? Looks like you are the most competent in that 😀
@btc @noahsark769 @Usipov apology for the late reply. We are having a few refactoring to be able to merge things back to master. Beginning of this year we have completed swift support in buck and was in a process of merging back to master. Tricky part is that at the time we had it, swift didn't have support for creating static library afaik. Beside, we couldn't afford having too many frameworks for our app because it significantly reduces the app performance, so we came up with a solution to create a single app by linking all objects file of swift targets together in one shot. To support this solution, we made quite a few unconventional commits on our buck fork and therefore we are paying it back by trying to do thing right. We have a plan to bring what we have to master, but let's discuss further on our slack group.
swift didn't have support for creating static library afaik
FWIW, you could always create Swift static libraries manually, or with SwiftPM, the only problem here is that Xcode has never (and still doesn't) support doing this.
@nguyentruongtho How does one find this Slack group? Is it open to the public? Also is there a branch, would love to take a look.
It's semi-open; you can sign up automatically if you're with certain companies: https://buckbuild.slack.com/
Otherwise drop me a line at markwang at fb dot com and I can send you an invite.
Wondering what the status is on this, sorry if I missed something
I am now trying to understand if buck is ready for mixed obj-c and swift project @nguyentruongtho @fkorotkov do you have any updates on the swift support?
@adc-amatosov my guess is that a better person to ask would be @milend as he is pushing a lot of Swift-related changes since August.
Thanks @vhbit Hi @milend, I am looking forward to use Buck, but a bit concern with the swift support. Can you please help me figuring out what is the current state of the swift support for obj-c and swift mixed projects?
@adc-amatosov Check out: https://medium.com/airbnb-engineering/building-mixed-language-ios-project-with-buck-8a903b0e3e56
Thanks @FredLoh
I think the current state is the you can mix Swift and Objective-C in an apple_library rule. Can you verify that everything works for you?
I will try to migrate one small mixed language project in next couple days and will keep you posted. If it works well I will try to migrate something bigger. What I still need to figure out is what to do with 3rd parties. We have a ton of 3rd party deps that get installed via CocoaPods now and most of them don't have other distribution channels. Some of them are source based, some prebuild static frameworks, some prebuild dynamic frameworks. But this is totally offtop for this ticket
Mixing Swift + Obj-C is supported but there are multiple caveats:
- It's only officially supported for
apple_library
andapple_test
.apple_binary
is not supported at present. - In order to enable the support, you have to set
apple.use_swift_delegate
config option tofalse
. - Currently only a Swift static lib workflow is supported (i.e., non-modular). This means you cannot import your
apple_library
in Obj-C through@import Module;
- You must set
swift_version
of theapple_library
so that project generation works. - You lose the ability to debug the Swift code (i.e., no local vars + ability to print using
lldb
). This is due to SR-2660. - In order to expose Obj-C code, you must use bridging headers.
- If you want to understand the latest Swift support, the best source of truth is the integration suite.
Thanks for very detailed update @milend
You lose the ability to debug the Swift code
This looks like pretty severe to me. Are there any workaround for this?
This looks like pretty severe to me. Are there any workaround for this?
There are no workarounds. Breakpoints still work, though, you just lose the ability to inspect variables (either through Xcode's debugger view or via lldb
).
@milend Is that because Swift librairies are compiled as static libs? Any hope to have the debugger working for static libraries at some point or is that just never going to work until the dynamic framework workflow is implemented?
Is that because Swift librairies are compiled as static libs?
Yes and no. The usage of static libs exposes an underlying bug. The details are documented in SR-2660.
Any hope to have the debugger working for static libraries at some point or is that just never going to work until the dynamic framework workflow is implemented?
It's just a question of getting SR-2660 addressed, so that it starts working. Given that Xcode 9 added official support for Swift static libs, I believe this would be addressed in the future. But that's just my guess, I'm not aware of anyone addressing the bug at present.
One aspect I forgot to mention is that you can still debug a single Swift module at a time (not ideal but at least it's something). This can be done by adding something like the following to the linker flags for the final binary (macOS example):
-Xlinker -add_ast_path -Xlinker $BUILT_PRODUCTS_DIR/Module.swiftmodule/x86_64.swiftmodule
I managed to get my apple_binary and all dependencies building properly for a swift app but am getting a weird error when it tries to launch on simulator/device. Any idea whats going on here?

@KieranLafferty I encountered the same issue when the apple_binary
did not actually have any source files (all the code was in a mixed apple_library
with an @NSApplicationMain
). The solution is to just have a simple main.m
in the apple_binary
that bootstraps your app (UIApplicationMain()
in your case).
Ok! So the app now launches and runs the main.m but I'm unable to make reference to my AppDelegate file that's contained in the apple_library.
I've attempted the following with no success
#import <UIKit/UIKit.h>
#import <CompanyLibrary/CompanyLibrary-swift.h>
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
This gets very close and code completion works for this file but I get a compilation error because the #import <CompanyLibrary/CompanyLibrary-swift.h> contains a reference to @import MyCustomLib;
which complains Module 'MyCustomLib' can not be found
I also tried simply going into <CompanyLibrary/CompanyLibrary-swift.h> and finding the class definition for my AppDelegate class which I found to have the following definition:
SWIFT_CLASS("_TtC11YarnLibrary11AppDelegate")
@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>
I thought that simply copying the string _TtC11YarnLibrary11AppDelegate
to main.m's UIApplicationMain(argc, argv, nil, @"_TtC11YarnLibrary11AppDelegate");
but that didn't work either.
Definitely getting closer but would be great to hear how you got it to load your AppDelegate that was contained in dependency
Note:
I've also removed @UIApplicationMain
from my AppDelegate file and added the following to my buck config as per your comment
[apple]
use_swift_delegate=false
Hi @KieranLafferty, the real name of your swift class from Obj-C is "CompanyLibrary.AppDelegate"
You can also change it by adding @objc(AppDelegate)
in front of class AppDelegate
in swift file. In this case name of the swift class will be "AppDelegate"
on the obj-c side
Thanks for the response @adc-amatosov, I tried what you suggested (with and without @objc before the class) and didn't have any luck.
Without @objc in AppDelegate
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, @"CompanyLibrary.AppDelegate");
}
}
With @objc in AppDelegate
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, @"AppDelegate");
}
}
In both situations I get the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to instantiate the UIApplication delegate instance. No class named AppDelegate is loaded.'
That is weird. Are you sure your swift library links successfully to the app binary?
You can check it using nm -gU "/path/to/your/Name.app/Name" | grep AppDelegate
@KieranLafferty The names of Obj-C classes defined in Swift are mangled. You will usually something like:
SWIFT_CLASS("_TtC9ComponentLibrary21AppDelegate")
@interface AppDelegate: NSObject
...
@end
Instead, you should do the following:
- In your
main.m
file, do#import <ComponentLibrary/ComponentLibrary-Swift.h>
(whereComponentLibrary
is the name of the mixedapple_library
) - Use
NSStringFromClass([AppDelegate class])
in the call toUIApplicationMain
, not a literal string. - You must remember to annotate your
AppDelegate
in Swift with@objc
, in addition to subclassingNSObject
.
Tried that too but it can’t compile the Lib-swift.h (see my previous comment above) because the header tried to import another swift library of mine using @import which isn’t supported yet by buck
@KieranLafferty:
it can’t compile the Lib-swift.h (see my previous comment above) because the header tried to import another swift library of mine using @import which isn’t supported yet by buck
There's a trick to workaround this. Instead of importing <ComponentLib/ComponentLib-Swift.h>
, you create a header like <ComponentLib/ComponentLib-Public-Swift.h>
inside of which you do:
#import <LibA/Header.h> // LibA is the module which you imported via `import` in Swift
#import <ComponentLib/ComponentLib-Public-Swift.h>
ComponentLib-Public-Swift.h
effectively ensures that any deps from the Swift side of ComponentLib
which are exposed to Obj-C are always imported beforehand.
Then whenever you want to import ComponentLib-Swift.h
, instead use ComponentLib-Public-Swift.h
.
@milend Did you mean to say
#import <LibA/Header.h> // LibA is the module which you imported via `import` in Swift
#import <ComponentLib/ComponentLib-Swift.h>
?
I.e. the idea is to import private ComponentLib-Swift.h
via public header?
It has been almost a year since the last discussion on this. At that time it seemed possible to build mixed-language projects but the process wasn't very straightforward. What is the current state of Swift support and mixed Swift-ObjC projects in Buck? Would using Buck be straightforward if we split mixed projects into language-specific projects?
Currently there is no clean documentation about Swift support in Buck. It supports it up to some degree and I personally don't know what is that degree (e.g. does it support Swift 4/4.1/4.2?), but you can check out the unit tests as they have some clues how would you combine ObjC and Swift in a single module. The rest of the information is available in this thread. I wish it could be structured somehow on buckbuild.com. This is what we have now.