swift-haskell-tutorial
swift-haskell-tutorial copied to clipboard
Direction and Scope
The first choice to make for the direction of the tutorial is whether to let GHC or swiftc build the final executable. I chose to let Xcode and swiftc build the executable, but having GHC link to a Swift framework might be a good choice too. Reasons to embed the Haskell library include
- GHC linking will probably change less than Swift in the future. Swift is likely to change as its ABI story progresses.
- Xcode automatically bundles the right Swift dylibs for you.
- Xcode is always updated with all app bundle features (icons, signing, Info.plist editor).
- Xcode knows exactly how all the Swift code is built and can provide correct code completion and debugging.
- If Haskell is being used as a backend for a Swift frontend, being able to directly call into linked Haskell functions is probably more useful than the reverse.
- I've embedded Haskell libraries before.
Reasons not to:
- Dynamic library dependencies need to be chased and copied to the bundle. If GHC builds the executable, it can link them all statically. This is solvable with tooling like
cabal-macosx. - Haskell can't call directly into the app's Swift functions by linking. (Although it can still call system frameworks.)
The Xcode project currently includes stack build as a build script phase, but this could be inverted by having Setup.hs call xcodebuild.
The tutorial currently only covers passing integers from Swift to Haskell, but I'd like to also cover passing bytestrings and functions in both directions.
Ok, I'm having trouble giving you a good recommendation here. I was in fact expecting the first option because in my case most of the development happens on the Haskell side, I don't expect to be calling a lot of Swift functions from Haskell.
Yes, I do think going Swift -> Haskell is generally the better option, but wanted to document some reasons why the other way could or couldn't work too.
I mean I think if you can make it work or explain the issue well, that's useful.
Sorry for the delay. Attempting to go through the tutorial and so far when I get to the build after setting up runNSApplication. I get the following:
biglambda$ stack build
CruxLibrary-0.1.0.0: build
Preprocessing executable 'Crux' for CruxLibrary-0.1.0.0...
Linking .stack-work/dist/x86_64-osx/Cabal-1.24.2.0/build/Crux/Crux ...
ld: framework not found CruxLibrary
clang: error: linker command failed with exit code 1 (use -v to see invocation)
gcc' failed in phase Linker'. (Exit code: 1)
-- While building package CruxLibrary-0.1.0.0 using: /Users/biglambda/.stack/setup-exe-cache/x86_64-osx/setup-Simple-Cabal-1.24.2.0-ghc-8.0.2 --builddir=.stack-work/dist/x86_64-osx/Cabal-1.24.2.0 build exe:Crux --ghc-options " -ddump-hi -ddump-to-file" Process exited with code: ExitFailure 1
Not sure where to place the framework. My directory structure is the same as yours I think.
I did forget to mention to build the framework at the end of the Converting the Swift App to a Framework section, so you might just need to build the framework in Xcode before stack building. I've added that in now.
The framework should be symlinked to its location in the Xcode build directory from the project's build/ directory. The symlink should be created by the Run Script phase added at the end of the Converting the Swift App to a Framework section.
Here's what's in my project's build directory after a successful build:
# tree -a build
build
├── SwiftAppLibrary.framework -> /Users/nanotech/Library/Developer/Xcode/DerivedData/SwiftHaskell-cwfykomvmcuamcbfwulxhcfjpbdi/Build/Products/Debug/SwiftAppLibrary.framework
├── SwiftHaskell -> ../.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/SwiftHaskell/SwiftHaskell
└── ghc
└── include -> /Users/nanotech/.stack/programs/x86_64-osx/ghc-8.0.1/bin/../lib/ghc-8.0.1/include
3 directories, 1 file
Sorry for this, I'm pretty new to XCode, I haven't been able to build the framework, getting some strange errors in XCode:
-- file:///Users/biglambda/Software/crux/xcode/.DS_Store: warning: Missing file: /Users/biglambda/Software/crux/xcode/.DS_Store is missing from working copy
-- PBXCp build/Crux /Users/biglambda/Library/Developer/Xcode/DerivedData/Crux-henfnnwgwlypjocyelgjcuwrmuzb/Build/Products/Debug/CruxLibrary.framework/Versions/A/Crux cd /Users/biglambda/Software/crux/xcode/Crux builtin-copy -exclude .DS_Store -exclude CVS -exclude .svn -exclude .git -exclude .hg -resolve-src-symlinks /Users/biglambda/Software/crux/xcode/Crux/build/Crux /Users/biglambda/Library/Developer/Xcode/DerivedData/Crux-henfnnwgwlypjocyelgjcuwrmuzb/Build/Products/Debug/CruxLibrary.framework/Versions/A
error: /Users/biglambda/Software/crux/xcode/Crux/build/../.stack-work/dist/x86_64-osx/Cabal-1.24.2.0/build/Crux/Crux: No such file or directory
-- file:///Users/biglambda/Software/crux/xcode/.DS_Store: warning: Missing file: /Users/biglambda/Software/crux/xcode/.DS_Store is missing from working copy
A crux/xcode/.DS_Store Finder display settings file was staged into git and then deleted from the working copy. I think running git rm crux/xcode/.DS_Store should clear this up. For the future, add .DS_Store to your global gitignore (at ~/.config/git/ignore by default).
-- PBXCp build/Crux ... No such file or directory
Xcode is trying to copy the Haskell executable into the app bundle here, but it isn't built yet. You don't need to build the app bundle yet though, just the framework, so set the framework as the current target before building:

The tutorial describes the build ordering a bit later in the middle of Linking to the Executable.
That doesn't seem to solve the "PBXCp build/Crux ... No such file or directory" error.
It says in the tutorial to make SwiftAppLibrary the only target of AppDelegate.swift and MainMenu.xib. It seems greyed out for AppDelegate.swift
Can you check the Build Phases of the two targets? Target membership checkboxes can be greyed out if a Compile Sources phase is missing. For the framework, they should be
- Target Dependencies
- (empty)
- Run Script (optional)
stack build; bash link-deps.sh- (Input/output files don't matter for this issue)
- Compile Sources
- AppDelegate.swift (Source files can also be added to targets from here)
- SwiftAppLibrary.m
- Link Binary With Libraries
- (empty)
- Headers
- Public: SwiftAppLibrary.h
- Others: (empty)
- Copy Bundle Resources
- MainMenu.xib
- Run Script
set -u; ln -sf "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "${PROJECT_DIR}/build/"
For the app bundle,
- Target Dependencies
- SwiftAppLibrary
- Copy Bundle Resources
- Assets.xcassets
- Embed Frameworks
- Destination: Frameworks
- SwiftAppLibrary.framework
- Copy Files
- Destination: Executables
- SwiftHaskell
Substitute SwiftHaskell and SwiftAppLibrary with your Haskell executable and Swift framework names respectively.
The only time Xcode should be copying from build/../.stack-work/dist/x86_64-osx/Cabal-1.24.2.0/build/Crux/Crux is in the last Copy Files phase of the app bundle target.
Ok the library builds properly but the app still gives:
Unable to run command 'PBXCp Crux.app' - this target might include its own product. Unable to run command 'ValidateEmbeddedBinary Crux.app' - this target might include its own product. Unable to run command 'Touch Crux.app' - this target might include its own product. Unable to run command 'CodeSign Crux.app' - this target might include its own product. Unable to run command 'RegisterWithLaunchServices Crux.app' - this target might include its own product.
this target might include its own product
Is the app's Executable Copy Files phase perhaps set to copy Crux.app (the built app bundle and product of the app target) instead of build/Crux (the Haskell executable)?
Ok, seems to be building but crashes:
dyld: Library not loaded: @rpath/libswiftAppKit.dylib Referenced from: /Users/biglambda/Library/Developer/Xcode/DerivedData/Crux-henfnnwgwlypjocyelgjcuwrmuzb/Build/Products/Debug/CruxLibrary.framework/Versions/A/CruxLibrary Reason: image not found (lldb)
Here's the build phases: http://imgur.com/mcGGB8R
In the framework target's Build Settings, is Always Embed Swift Standard Libraries set to Yes?
Your build phases all look correct.
Here's the contents of the tutorial project's compiled app:
$ tree -a SwiftHaskell.app
SwiftHaskell.app
└── Contents
├── Frameworks
│ └── SwiftAppLibrary.framework
│ ├── Resources -> Versions/Current/Resources
│ ├── SwiftAppLibrary -> Versions/Current/SwiftAppLibrary
│ └── Versions
│ ├── A
│ │ ├── Frameworks
│ │ │ ├── libswiftAppKit.dylib
│ │ │ ├── libswiftCore.dylib
│ │ │ ├── libswiftCoreData.dylib
│ │ │ ├── libswiftCoreGraphics.dylib
│ │ │ ├── libswiftCoreImage.dylib
│ │ │ ├── libswiftDarwin.dylib
│ │ │ ├── libswiftDispatch.dylib
│ │ │ ├── libswiftFoundation.dylib
│ │ │ ├── libswiftIOKit.dylib
│ │ │ ├── libswiftObjectiveC.dylib
│ │ │ ├── libswiftQuartzCore.dylib
│ │ │ ├── libswiftSwiftOnoneSupport.dylib
│ │ │ └── libswiftXPC.dylib
│ │ ├── Resources
│ │ │ ├── Base.lproj
│ │ │ │ └── MainMenu.nib
│ │ │ ├── Info.plist
│ │ │ └── libswiftRemoteMirror.dylib
│ │ ├── SwiftAppLibrary
│ │ └── _CodeSignature
│ │ └── CodeResources
│ └── Current -> A
├── Info.plist
├── MacOS
│ └── SwiftHaskell
├── PkgInfo
├── Resources
└── _CodeSignature
└── CodeResources
14 directories, 23 files
Ok it was set to no. It's running now and showing the window. I'll go through the rest tonight and we can finish up the bounty. Basically all I think should happen to complete the tutorial is a little more explanation of why. Assume most haskellers have zero knowledge of Xcode or this method of linking so:
-- Each symlink you've created in your shell script should have an explanation of it's purpose. -- More explanation of why you chose the various settings in XCode. -- Links to further understanding in Apple documentation, books or elsewhere. Show us how to get this specialization. -- Include all of the information from this troubleshooting thread in the tutorial. -- Also you should submit a pull request to Daniel the cabal-macosx maintainer if you haven't already.
Great work and thanks.
Also I didn't quite use the same names as you did. When you "import SwiftHaskell" in your AppDelegate.swift, to which file are you referring? I see, it's in the module.modulemap file. Why would I get a AppDelegate.swift:20:32: Use of unresolved identifier 'square'
http://imgur.com/7Aso88N
Great!
-
Assuming absolutely zero Xcode knowledge would include a bit too much to put in this tutorial, I think. I think it's reasonable to expect the reader to have used Swift and Xcode previously, in at least a basic capacity, before attempting a fairly complex FFI integration. Even so, I've tried to improve the accessibility.
-
I've expanded on the symlink comments in the script.
-
Setting instructions now have more explanation.
I also switched the Always Embed Swift Standard Libraries setting to be set on the app bundle target instead of the framework. It works both ways, but this way is more consistent with other Swift apps.
-
Most of this is not documented by Apple, but I've linked to sources in the few places that have them.
-
I've added a troubleshooting section at the end.
-
I'll open PRs for the
cabal-macosxbranches.
Yes, import SwiftHaskell is referring to the name defined in the module.modulemap.
Use of unresolved identifier 'square'
The first warning
File 'AppDelegate.swift' is part of module 'Crux'; ignoring import
is the clue. The product/module/target that contains AppDelegate.swift is apparently named Crux, so the import is importing the current module, which is redundant. This seems wrong, since you also have a CruxLibrary framework target that AppDelegate.swift should be in instead. Check the Target Membership of the file.
Additionally, make sure the name in module.modulemap (the Haskell executable name) is different from CruxLibrary (the Swift framework name).
Sorry about this, I started over with a fresh project and tried to use all the exact same filenames trying very hard to follow all the details. I also tried cloning the repositorie. In the first case I can build the initial empty window but when I try adding the label that references square I get this error: (I also tried building the cloned project file and got a similar error)
/Users/biglambda/Library/Developer/Xcode/DerivedData/SwiftHaskell-gbesrmujipgqmsdlyyeowmwpsjfu/Build/Intermediates/SwiftHaskell.build/Debug/SwiftAppLibrary.build/unextended-module.modulemap:2:19: error: umbrella header 'SwiftAppLibrary.h' not found
umbrella header "SwiftAppLibrary.h"
^
Here is my entire tree: http://pastebin.com/jT3Ycd6K
error: use of unresolved identifier 'square'
This appears to be from Xcode incorrectly caching some previous state of the SwiftHaskell module before it was fully configured. Performing a full clean (Product » Clean Build Folder... ⌥⇧⌘K) and rebuilding fixes this for me, and it doesn't appear again. I've only been able to reproduce this when starting the tutorial fresh through to the end.
I've added this workaround to the tutorial.
error: umbrella header 'SwiftAppLibrary.h' not found
This was occurring in the tutorial project because I had the Enable Modules setting incorrectly disabled. The default is enabled in newly created Swift projects.
I didn't notice earlier because it only fails on the first build after a (normal) clean. It would work after the first build because the header copy phase is after the compile phase, and the compile phase would use the copied header from the previous build. Enabling modules seems to either set the internal include paths correctly or also copy the header before compiling.
I wasn't able to reproduce this in a fresh project, but, see what cleaning the build folder does for this. These Stack Overflow answers have some other possibilities, although none of them should apply in a fresh project following the tutorial.
Ok, that works but it leads to a runtime error: http://imgur.com/1Z7oIbu
hello world fatal error: unexpectedly found nil while unwrapping an Optional value 2017-02-25 21:33:30.126607 SwiftHaskell[74938:2734572] fatal error: unexpectedly found nil while unwrapping an Optional value Current stack trace: 0 libswiftCore.dylib 0x000000010044fce0 swift_reportError + 132 1 libswiftCore.dylib 0x000000010046d090 _swift_stdlib_reportFatalError + 61 2 libswiftCore.dylib 0x00000001002630c0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 355 3 libswiftCore.dylib 0x00000001003df230 partial apply for (_fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never).(closure #2) + 109 4 libswiftCore.dylib 0x00000001002630c0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 355 5 libswiftCore.dylib 0x00000001003973f0 specialized _fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never + 96 6 SwiftAppLibrary 0x000000010020d530 AppDelegate.applicationDidFinishLaunching(Notification) -> () + 118 7 SwiftAppLibrary 0x000000010020d790 @objc AppDelegate.applicationDidFinishLaunching(Notification) -> () + 71 8 CoreFoundation 0x00007fff87f85590 CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER + 12 9 CoreFoundation 0x00007fff87f852f0 _CFXRegistrationPost + 427 10 CoreFoundation 0x00007fff87f851d0 ___CFXNotificationPost_block_invoke + 50 11 CoreFoundation 0x00007fff87f421e0 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1827 12 CoreFoundation 0x00007fff87f416e0 _CFXNotificationPost + 604 13 Foundation 0x00007fff899510a1 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66 14 AppKit 0x00007fff85d37d74 -[NSApplication _postDidFinishNotification] + 297 15 AppKit 0x00007fff85d37b32 -[NSApplication _sendFinishLaunchingNotification] + 208 16 AppKit 0x00007fff85bfb94b -[NSApplication(NSAppleEventHandling) _handleAEOpenEvent:] + 552 17 AppKit 0x00007fff85bfb532 -[NSApplication(NSAppleEventHandling) _handleCoreEvent:withReplyEvent:] + 661 18 Foundation 0x00007fff8999c43b -[NSAppleEventManager dispatchRawAppleEvent:withRawReply:handlerRefCon:] + 290 19 Foundation 0x00007fff8999c371 _NSAppleEventManagerGenericHandler + 102 20 AE 0x00007fff88da424a aeDispatchAppleEvent(AEDesc const*, AEDesc*, unsigned int, unsigned char*) + 544 21 AE 0x00007fff88da41ba dispatchEventAndSendReply(AEDesc const*, AEDesc*) + 39 22 AE 0x00007fff88da3fb5 aeProcessAppleEvent + 312 23 HIToolbox 0x00007fff8751d888 AEProcessAppleEvent + 55 24 AppKit 0x00007fff85bf6734 _DPSNextEvent + 1811 25 AppKit 0x00007fff8630bb5e -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1637 26 AppKit 0x00007fff85beb19f -[NSApplication run] + 926 27 SwiftAppLibrary 0x000000010020d1f0 runNSApplication + 258 28 SwiftHaskell 0x0000000100000e30 c3t4_info + 95 (lldb)
You need to add the label in interface builder and connect it to the AppDelegate's outlet.
Add a new label to the window in MainMenu.xib for us to write the result of our Haskell function square into
Granted, the instruction is a little brief compared to the rest of the tutorial. I'll expand it.
I've added a more detailed explanation on how to add and connect the label with Interface Builder.
Cool, I'm all the way through. I sent the rest of the bounty.
Fantastic! Thanks.