nanotech / swift-haskell-tutorial

Integrating Haskell with Swift Mac Apps
BSD 2-Clause "Simplified" License
149 stars 12 forks source link

Direction and Scope #1

Open nanotech opened 7 years ago

nanotech commented 7 years ago

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

Reasons not to:

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.

biglambda commented 7 years ago

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.

nanotech commented 7 years ago

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.

biglambda commented 7 years ago

I mean I think if you can make it work or explain the issue well, that's useful.

biglambda commented 7 years ago

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 phaseLinker'. (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.

nanotech commented 7 years ago

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
biglambda commented 7 years ago

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

nanotech commented 7 years ago

-- 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:

Framework target

The tutorial describes the build ordering a bit later in the middle of Linking to the Executable.

biglambda commented 7 years ago

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

nanotech commented 7 years ago

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

For the app bundle,

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.

biglambda commented 7 years ago

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.

nanotech commented 7 years ago

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)?

biglambda commented 7 years ago

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

nanotech commented 7 years ago

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
biglambda commented 7 years ago

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.

biglambda commented 7 years ago

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

nanotech commented 7 years ago

Great!


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).

biglambda commented 7 years ago

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" ^

:0: error: underlying Objective-C module 'SwiftAppLibrary' not found /Users/biglambda/Software/SwiftHaskell/SwiftHaskell/AppDelegate.swift:18:32: error: use of unresolved identifier 'square' label.stringValue = "\(square(5))" ^~~~~~ :0: warning: 'cacheParamsComputed' is deprecated :0: warning: 'cacheAlphaComputed' is deprecated :0: warning: 'keepCacheWindow' is deprecated :0: error: 'memoryless' is unavailable Metal.MTLCommandBufferError:55:14: note: 'memoryless' has been explicitly marked unavailable here case memoryless Here is my entire tree: http://pastebin.com/jT3Ycd6K
nanotech commented 7 years ago

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.

biglambda commented 7 years ago

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 ((UnsafeBufferPointer) -> 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 ((UnsafeBufferPointer) -> 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)

nanotech commented 7 years ago

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.

nanotech commented 7 years ago

I've added a more detailed explanation on how to add and connect the label with Interface Builder.

biglambda commented 7 years ago

Cool, I'm all the way through. I sent the rest of the bounty.

nanotech commented 7 years ago

Fantastic! Thanks.