Complete the linking tutorial
@@ -0,0 +1,413 @@
|
||||
# Integrating Haskell with Swift Mac Apps
|
||||
|
||||
To start, create a new Cocoa Application Xcode project
|
||||
with Swift as the default language:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Then `cd` into the directory with the `.xcodeproj` and create a
|
||||
new stack project:
|
||||
|
||||
```sh
|
||||
$ cd SwiftHaskell
|
||||
$ stack new SwiftHaskellLibrary simple
|
||||
```
|
||||
|
||||
Move these files up to the top directory, so we can run all of
|
||||
our commands from the same directory:
|
||||
|
||||
```sh
|
||||
$ mv -vn SwiftHaskellLibrary/* .
|
||||
$ rmdir SwiftHaskellLibrary
|
||||
```
|
||||
|
||||
In `SwiftHaskellLibrary.cabal`, rename the executable to
|
||||
match the Xcode app's name of SwiftHaskell:
|
||||
|
||||
```cabal
|
||||
executable SwiftHaskell
|
||||
```
|
||||
|
||||
To combine our Haskell library with our Swift UI, we'll build
|
||||
the Swift app as a framework and link to it from the Haskell
|
||||
executable. Xcode will then package both up into an app bundle.
|
||||
|
||||
The reason for doing the linking in this direction is that
|
||||
building a self-contained dynamic library is currently simpler
|
||||
with Swift and Xcode than it is with Cabal.
|
||||
|
||||
## Exporting Haskell Functions
|
||||
|
||||
Here's the trivial function `square` that we'll export as a
|
||||
simple first example:
|
||||
|
||||
```haskell
|
||||
square x = x * x
|
||||
```
|
||||
|
||||
Haskell functions exported via the FFI can only contain
|
||||
certain types in their signatures that are compatible with C:
|
||||
primitive integers, floats and doubles, and pointer types.
|
||||
The full list is in [section 8.7 of the Haskell
|
||||
Report][haskell-report-8.7].
|
||||
|
||||
Since we'll only be using `square` to demonstrate the FFI, let's
|
||||
assign it a FFI-compatible type directly. For more complex
|
||||
functions, wrap them in a new function and convert their inputs
|
||||
and outputs as needed.
|
||||
|
||||
```haskell
|
||||
import Foreign.C
|
||||
|
||||
square :: CInt -> CInt
|
||||
square x = x * x
|
||||
```
|
||||
|
||||
To export `square`, add a `foreign export` definition with a
|
||||
calling convention of `ccall`:
|
||||
|
||||
```haskell
|
||||
foreign export ccall square :: CInt -> CInt
|
||||
```
|
||||
|
||||
For the full syntax of `foreign export`, see [section 8.3 of the
|
||||
Haskell Report][haskell-report-8.3].
|
||||
|
||||
[haskell-report-8.7]: https://www.haskell.org/onlinereport/haskell2010/haskellch8.html#x15-1700008.7
|
||||
[haskell-report-8.3]: https://www.haskell.org/onlinereport/haskell2010/haskellch8.html#x15-1530008.3
|
||||
|
||||
Together, `src/Main.hs` is
|
||||
|
||||
```haskell
|
||||
module Main where
|
||||
|
||||
import Foreign.C
|
||||
|
||||
foreign export ccall square :: CInt -> CInt
|
||||
|
||||
square :: CInt -> CInt
|
||||
square x = x * x
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "hello world"
|
||||
```
|
||||
|
||||
## Importing Haskell's Generated FFI Headers into Swift
|
||||
|
||||
If we now `stack build`, in addition to building the library,
|
||||
GHC will generate C header files for each module with foreign
|
||||
exports. Because these are build artifacts, they're buried
|
||||
somewhat deep in the file hierarchy, but we can ask `stack`
|
||||
and `find` where they are:
|
||||
|
||||
$ find "$(stack path --dist-dir)" -name Main_stub.h
|
||||
.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/SwiftHaskellLibrary/SwiftHaskellLibrary-tmp/Main_stub.h
|
||||
|
||||
These stub headers `#include "HsFFI.h"` from GHC, so we'll also
|
||||
need to find the current compiler's version of that header.
|
||||
|
||||
$ find "$(stack path --compiler-bin)/.." -name HsFFI.h
|
||||
/Users/nanotech/.stack/programs/x86_64-osx/ghc-8.0.1/bin/../lib/ghc-8.0.1/include/HsFFI.h
|
||||
|
||||
Since we'll be importing these headers into a Swift framework,
|
||||
we won't be able to use `#include` as we would in C. Instead,
|
||||
Swift uses [Clang's module format][clang-modules]. (Swift
|
||||
applications can use [bridging headers][swift-bridging-headers],
|
||||
but frameworks [must use modules][so-non-modular-header].) A
|
||||
`module.modulemap` file to import `Main_stub.h` looks like
|
||||
|
||||
module SwiftHaskell {
|
||||
header "Main_stub.h"
|
||||
export *
|
||||
}
|
||||
|
||||
[swift-bridging-headers]: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-ID156
|
||||
[so-non-modular-header]: https://stackoverflow.com/questions/24103169/swift-compiler-error-non-modular-header-inside-framework-module/37072619#37072619
|
||||
[clang-modules]: http://clang.llvm.org/docs/Modules.html
|
||||
|
||||
As the paths to these headers vary, let's use a script to
|
||||
automatically copy them out and build a module map. We'll also
|
||||
create a symlink to the built executable's location for later.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
EXECUTABLE_NAME=SwiftHaskell
|
||||
DIST_DIR="$(stack path --dist-dir)"
|
||||
GHC_VERSION="$(stack exec -- ghc --numeric-version)"
|
||||
GHC_LIB_DIR="$(stack path --compiler-bin)/../lib/ghc-$GHC_VERSION"
|
||||
STUB_BUILD_DIR="${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}-tmp"
|
||||
STUB_MODULE_DIR="${EXECUTABLE_NAME}/include"
|
||||
STUB_MODULE_MAP="${STUB_MODULE_DIR}/module.modulemap"
|
||||
|
||||
# Create a module map from the generated Haskell
|
||||
# FFI export headers for importing into Swift.
|
||||
mkdir -p "${STUB_MODULE_DIR}"
|
||||
NL="
|
||||
"
|
||||
module_map="module ${EXECUTABLE_NAME} {${NL}"
|
||||
for h in $(find "${STUB_BUILD_DIR}" -name '*.h'); do
|
||||
h_filename="${h/$STUB_BUILD_DIR\//}"
|
||||
cp "$h" "${STUB_MODULE_DIR}/"
|
||||
module_map="${module_map} header \"${h_filename}\"${NL}"
|
||||
done
|
||||
module_map="${module_map} export *${NL}"
|
||||
module_map="${module_map}}"
|
||||
echo "${module_map}" > "${STUB_MODULE_MAP}"
|
||||
|
||||
# Symlink to the current GHC's header directory from a more
|
||||
# convenient place for Xcode to find.
|
||||
mkdir -p build/ghc
|
||||
ln -sf "${GHC_LIB_DIR}/include" build/ghc/
|
||||
|
||||
# Symlink to the Haskell executable for Xcode.
|
||||
ln -sf "../${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}" build/
|
||||
```
|
||||
|
||||
Save the script as `link-deps.sh`, run `stack build`, and then
|
||||
run `bash link-deps.sh` to prepare for the next section.
|
||||
|
||||
## Converting the Swift App to a Framework
|
||||
|
||||
Create a new Cocoa Framework target in the Xcode project
|
||||
named SwiftAppLibrary, then change the Target Membership of
|
||||
`AppDelegate.swift` and `MainMenu.xib` to only SwiftAppLibrary
|
||||
in Xcode's File Inspector in the right sidebar:
|
||||
|
||||

|
||||
|
||||
In the new framework's build settings, set **Always Embed Swift
|
||||
Standard Libraries** to **Yes**.
|
||||
|
||||
Drag the `SwiftHaskell` executable we built previously with
|
||||
Stack into Xcode from the `build/` directory that we symlinked
|
||||
it into, but do not add it to any targets when prompted:
|
||||
|
||||

|
||||
|
||||
In the SwiftHaskell app target's Build Phases, remove the
|
||||
**Compile Sources** and **Link Binary With Libraries**
|
||||
phases, and add a new **Copy Files** phase that copies the
|
||||
`SwiftHaskell` executable into the app bundle's Executables
|
||||
directory:
|
||||
|
||||

|
||||
|
||||
Finally, in the SwiftAppLibrary framework target's Build Phases,
|
||||
add a new **Run Script** phase to create a symlink to the built
|
||||
framework for us to link to from Cabal:
|
||||
|
||||
```sh
|
||||
set -u
|
||||
ln -sf "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "${PROJECT_DIR}/build/"
|
||||
```
|
||||
|
||||
## Linking to the Framework
|
||||
|
||||
Add these options to the executable's section in the `.cabal`
|
||||
file:
|
||||
|
||||
```cabal
|
||||
executable SwiftHaskell
|
||||
ghc-options: -threaded -framework-path build
|
||||
ld-options: -rpath @executable_path/../Frameworks
|
||||
frameworks: SwiftAppLibrary
|
||||
```
|
||||
|
||||
- `-threaded` enables the multithreaded GHC runtime, which is
|
||||
usually what you want.
|
||||
- `-framework-path build` tells GHC to look for frameworks where we
|
||||
symlinked our framework to.
|
||||
- `-rpath @executable_path/../Frameworks` embeds into the
|
||||
executable where the dynamic linker should look for shared
|
||||
libraries.
|
||||
|
||||
## Starting the GUI
|
||||
|
||||
Because Haskell has control over the program's entry point
|
||||
(`main`), we'll need to have it call out to Swift to
|
||||
start Cocoa's main thread. Add this top level function to
|
||||
`AppDelegate.swift`:
|
||||
|
||||
```swift
|
||||
@_cdecl("runNSApplication")
|
||||
func runNSApplication() {
|
||||
let app = NSApplication.shared()
|
||||
var topObjects: NSArray = []
|
||||
NSNib.init(nibNamed: "MainMenu", bundle: Bundle(for: AppDelegate.self))!
|
||||
.instantiate(withOwner: app, topLevelObjects: &topObjects)
|
||||
app.run()
|
||||
}
|
||||
```
|
||||
|
||||
`@_cdecl` sets the symbol name of the Swift function, overriding
|
||||
normal symbol mangling.
|
||||
|
||||
In `SwiftAppLibrary.h`, add a `FOUNDATION_EXPORT` C prototype
|
||||
for the function, so that its symbol gets marked as public and
|
||||
is exported from the framework:
|
||||
|
||||
```c
|
||||
FOUNDATION_EXPORT void runNSApplication(void);
|
||||
```
|
||||
|
||||
In `Main.hs`, import the foreign function and call it from
|
||||
the end of `main`:
|
||||
|
||||
```haskell
|
||||
module Main where
|
||||
|
||||
import Foreign.C
|
||||
|
||||
foreign export ccall square :: CInt -> CInt
|
||||
|
||||
square :: CInt -> CInt
|
||||
square x = x * x
|
||||
|
||||
foreign import ccall "runNSApplication" runNSApplication :: IO ()
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "hello world"
|
||||
runNSApplication
|
||||
```
|
||||
|
||||
`runNSApplication` will not return, being busy with Cocoa's
|
||||
main run loop. Use `Control.Concurrent.forkIO` before calling
|
||||
`runNSApplication` to run other tasks as needed.
|
||||
|
||||
Run `stack build`, and build and run the `SwiftHaskell` app
|
||||
target in Xcode to launch the app and see the default window
|
||||
from `MainMenu.xib`:
|
||||
|
||||

|
||||
|
||||
## Completing Linking Setup
|
||||
|
||||
Add `$(PROJECT_DIR)/SwiftHaskell/include` to the framework
|
||||
target's **Swift Compiler - Search Paths, Import Paths** setting
|
||||
in Xcode,
|
||||
|
||||

|
||||
|
||||
and `$(PROJECT_DIR)/build/ghc/include` to the framework's **User
|
||||
Header Search Paths** setting:
|
||||
|
||||

|
||||
|
||||
In order for the framework to be able to link to symbols in the
|
||||
Haskell executable, we need to tell the linker to leave symbols
|
||||
undefined and have them be resolved at runtime.
|
||||
|
||||
Add `-undefined dynamic_lookup` to the framework's **Other
|
||||
Linker Flags** setting.
|
||||
|
||||
Be aware that this means that link errors will occur at runtime
|
||||
instead of at link time. Also note that the framework linking
|
||||
to symbols in the executable (and depending on the generated
|
||||
headers), and the executable linking to the framework, creates
|
||||
a circular dependency. When initially building the project, you
|
||||
will need to build the components in this order:
|
||||
|
||||
- `stack build` to generate the Haskell FFI export headers.
|
||||
Linking will fail, as the Swift framework is not built yet.
|
||||
- Build the Swift framework.
|
||||
- `stack build`
|
||||
- Build the app bundle.
|
||||
|
||||
The first step can be skipped subsequently by committing the
|
||||
generated headers to source control.
|
||||
|
||||
To help automate this, add a new **Run Script** build phase to
|
||||
the beginning of the framework's build phases with the contents
|
||||
|
||||
```sh
|
||||
stack build
|
||||
bash link-deps.sh
|
||||
```
|
||||
|
||||
Add the Haskell sources as input files:
|
||||
|
||||
```
|
||||
$(PROJECT_DIR)/src/Main.hs
|
||||
$(PROJECT_DIR)/SwiftHaskellLibrary.cabal
|
||||
```
|
||||
|
||||
And the executable as an output file:
|
||||
|
||||
```
|
||||
$(PROJECT_DIR)/build/SwiftHaskell
|
||||
```
|
||||
|
||||
TODO: `xcodebuild` from `Setup.hs` hooks?
|
||||
|
||||
## Calling Haskell from Swift
|
||||
|
||||
We're now ready to use exported Haskell functions from Swift.
|
||||
Import `SwiftHaskell` at the top of `AppDelegate.swift`
|
||||
|
||||
```swift
|
||||
import SwiftHaskell
|
||||
```
|
||||
|
||||
Add a new label to the window in `MainMenu.xib` for us to write
|
||||
the result of our Haskell function `square` into, and add it as
|
||||
an `@IBOutlet` to the `AppDelegate`:
|
||||
|
||||
```swift
|
||||
@IBOutlet weak var label: NSTextField!
|
||||
```
|
||||
|
||||
We already have our Haskell library's header imported, so we
|
||||
can just call the exported `square` function. Add this to
|
||||
`applicationDidFinishLaunching`:
|
||||
|
||||
```swift
|
||||
label.stringValue = "\(square(5))"
|
||||
```
|
||||
|
||||
The final contents of `AppDelegate.swift` are:
|
||||
|
||||
```swift
|
||||
import Cocoa
|
||||
import SwiftHaskell
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@IBOutlet weak var window: NSWindow!
|
||||
@IBOutlet weak var label: NSTextField!
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
label.stringValue = "\(square(5))"
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("swiftAppMain")
|
||||
func swiftAppMain() {
|
||||
let app = NSApplication.shared()
|
||||
var topObjects: NSArray = []
|
||||
NSNib.init(nibNamed: "MainMenu", bundle: Bundle(for: AppDelegate.self))!
|
||||
.instantiate(withOwner: app, topLevelObjects: &topObjects)
|
||||
app.run()
|
||||
}
|
||||
```
|
||||
|
||||
Running the app,
|
||||
|
||||

|
||||
|
||||
## Strings and ByteString FFI
|
||||
|
||||
TODO
|
||||
|
||||
## Function and Closure FFI
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module SwHaLib {
|
||||
export *
|
||||
}
|
||||
@@ -7,25 +7,23 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import SwiftHaskell
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@IBOutlet weak var window: NSWindow!
|
||||
@IBOutlet weak var label: NSTextField!
|
||||
|
||||
static var square: (@convention(c) (CInt) -> CInt)! = nil
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
label.stringValue = "\(AppDelegate.square(5))"
|
||||
label.stringValue = "\(square(5))"
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("swiftAppMain")
|
||||
func swiftAppMain(square: @escaping @convention(c) (CInt) -> CInt) {
|
||||
AppDelegate.square = square
|
||||
@_cdecl("runNSApplication")
|
||||
func runNSApplication() {
|
||||
let app = NSApplication.shared()
|
||||
var topObjects: NSArray = []
|
||||
NSNib.init(nibNamed: "MainMenu", bundle: Bundle(for: AppDelegate.self))!
|
||||
|
||||
@@ -14,8 +14,6 @@ FOUNDATION_EXPORT double SwiftAppLibraryVersionNumber;
|
||||
//! Project version string for SwiftAppLibrary.
|
||||
FOUNDATION_EXPORT const unsigned char SwiftAppLibraryVersionString[];
|
||||
|
||||
FOUNDATION_EXPORT void swiftAppMain(void);
|
||||
FOUNDATION_EXPORT void runNSApplication(void);
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <SwiftAppLibrary/PublicHeader.h>
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
BFABC4CD1E4D627E006036C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFABC4CC1E4D627E006036C6 /* AppDelegate.swift */; };
|
||||
BFABC4D21E4D664D006036C6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFABC4D01E4D664D006036C6 /* MainMenu.xib */; };
|
||||
BFABC4E01E4D781D006036C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFABC4DF1E4D781D006036C6 /* Assets.xcassets */; };
|
||||
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in CopyFiles */ = {isa = PBXBuildFile; fileRef = BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = BFABC45B1E4C1DD1006036C6 /* SwiftAppLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in Copy Files */ = {isa = PBXBuildFile; fileRef = BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFABC45B1E4C1DD1006036C6 /* SwiftAppLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -26,24 +26,26 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
BFABC4EC1E4D78DF006036C6 /* CopyFiles */ = {
|
||||
BFABC4EC1E4D78DF006036C6 /* Copy Files */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 6;
|
||||
files = (
|
||||
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in CopyFiles */,
|
||||
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in Copy Files */,
|
||||
);
|
||||
name = "Copy Files";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BFABC4EF1E4D7948006036C6 /* CopyFiles */ = {
|
||||
BFABC4EF1E4D7948006036C6 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in CopyFiles */,
|
||||
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
@@ -128,11 +130,12 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = BFABC4631E4C1DD1006036C6 /* Build configuration list for PBXNativeTarget "SwiftAppLibrary" */;
|
||||
buildPhases = (
|
||||
BF2EA5CA1E5030E100651018 /* stack build and link-deps */,
|
||||
BFABC4561E4C1DD1006036C6 /* Sources */,
|
||||
BFABC4571E4C1DD1006036C6 /* Frameworks */,
|
||||
BFABC4581E4C1DD1006036C6 /* Headers */,
|
||||
BFABC4591E4C1DD1006036C6 /* Resources */,
|
||||
BFABC4CE1E4D6344006036C6 /* ShellScript */,
|
||||
BFABC4CE1E4D6344006036C6 /* Link framework to build */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -147,10 +150,9 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = BFABC4E51E4D781D006036C6 /* Build configuration list for PBXNativeTarget "SwiftHaskell" */;
|
||||
buildPhases = (
|
||||
BFABC4F11E4D81E8006036C6 /* ShellScript */,
|
||||
BFABC4D51E4D781D006036C6 /* Resources */,
|
||||
BFABC4EF1E4D7948006036C6 /* CopyFiles */,
|
||||
BFABC4EC1E4D78DF006036C6 /* CopyFiles */,
|
||||
BFABC4EF1E4D7948006036C6 /* Embed Frameworks */,
|
||||
BFABC4EC1E4D78DF006036C6 /* Copy Files */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -221,7 +223,26 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
BFABC4CE1E4D6344006036C6 /* ShellScript */ = {
|
||||
BF2EA5CA1E5030E100651018 /* stack build and link-deps */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(PROJECT_DIR)/src/Lib.hs",
|
||||
"$(PROJECT_DIR)/src/Main.hs",
|
||||
"$(PROJECT_DIR)/SwiftHaskellLibrary.cabal",
|
||||
);
|
||||
name = "stack build and link-deps";
|
||||
outputPaths = (
|
||||
"$(PROJECT_DIR)/build/SwiftHaskell",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "stack build\nbash link-deps.sh";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BFABC4CE1E4D6344006036C6 /* Link framework to build */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -229,25 +250,12 @@
|
||||
inputPaths = (
|
||||
"$(BUILT_PRODUCTS_DIR)/$(FULL_PRODUCT_NAME)",
|
||||
);
|
||||
name = "Link framework to build";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "set -eu\nrsync -a \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\" \"${PROJECT_DIR}\"/";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BFABC4F11E4D81E8006036C6 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "stack build\n./symlink_deps.sh";
|
||||
shellScript = "set -u\nln -sf \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\" \"${PROJECT_DIR}/build/\"";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
@@ -288,10 +296,10 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
@@ -340,10 +348,10 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
@@ -384,7 +392,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
@@ -395,11 +402,17 @@
|
||||
INFOPLIST_FILE = SwiftAppLibrary/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"-undefined",
|
||||
dynamic_lookup,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.nanotechcorp.SwiftAppLibrary;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/SwiftHaskell/include";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/build/ghc/include";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -407,7 +420,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
@@ -418,10 +430,16 @@
|
||||
INFOPLIST_FILE = SwiftAppLibrary/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"-undefined",
|
||||
dynamic_lookup,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.nanotechcorp.SwiftAppLibrary;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/SwiftHaskell/include";
|
||||
SWIFT_VERSION = 3.0;
|
||||
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/build/ghc/include";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -477,6 +495,7 @@
|
||||
BFABC4E71E4D781D006036C6 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "HsFFI.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern HsInt32 square(HsInt32 a1);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module SwiftHaskell {
|
||||
header "Main_stub.h"
|
||||
export *
|
||||
}
|
||||
@@ -21,8 +21,7 @@ executable SwiftHaskell
|
||||
default-language: Haskell2010
|
||||
hs-source-dirs: src
|
||||
main-is: Main.hs
|
||||
other-modules: Lib
|
||||
ghc-options: -threaded -framework-path .
|
||||
ghc-options: -threaded -framework-path build
|
||||
ld-options: -rpath @executable_path/../Frameworks
|
||||
frameworks: SwiftAppLibrary
|
||||
build-depends: base >= 4.7 && < 5
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
EXECUTABLE_NAME=SwiftHaskell
|
||||
DIST_DIR="$(stack path --dist-dir)"
|
||||
GHC_VERSION="$(stack exec -- ghc --numeric-version)"
|
||||
GHC_LIB_DIR="$(stack path --compiler-bin)/../lib/ghc-$GHC_VERSION"
|
||||
STUB_BUILD_DIR="${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}-tmp"
|
||||
STUB_MODULE_DIR="${EXECUTABLE_NAME}/include"
|
||||
STUB_MODULE_MAP="${STUB_MODULE_DIR}/module.modulemap"
|
||||
|
||||
# Create a module map from the generated Haskell
|
||||
# FFI export headers for importing into Swift.
|
||||
mkdir -p "${STUB_MODULE_DIR}"
|
||||
NL="
|
||||
"
|
||||
module_map="module ${EXECUTABLE_NAME} {${NL}"
|
||||
for h in $(find "${STUB_BUILD_DIR}" -name '*.h'); do
|
||||
h_filename="${h/$STUB_BUILD_DIR\//}"
|
||||
cp "$h" "${STUB_MODULE_DIR}/"
|
||||
module_map="${module_map} header \"${h_filename}\"${NL}"
|
||||
done
|
||||
module_map="${module_map} export *${NL}"
|
||||
module_map="${module_map}}"
|
||||
echo "${module_map}" > "${STUB_MODULE_MAP}"
|
||||
|
||||
# Symlink to the current GHC's header directory from a more
|
||||
# convenient place for Xcode to find.
|
||||
mkdir -p build/ghc
|
||||
ln -sf "${GHC_LIB_DIR}/include" build/ghc/
|
||||
|
||||
# Symlink to the Haskell executable for Xcode.
|
||||
ln -sf "../${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}" build/
|
||||
@@ -1,15 +1,15 @@
|
||||
module Main (main) where
|
||||
module Main where
|
||||
|
||||
import Foreign
|
||||
import Foreign.C
|
||||
|
||||
import Lib
|
||||
foreign export ccall square :: CInt -> CInt
|
||||
|
||||
foreign import ccall "swiftAppMain" swiftAppMain :: FunPtr (CInt -> CInt) -> IO ()
|
||||
foreign import ccall "wrapper" wrapSquare :: (CInt -> CInt) -> IO (FunPtr (CInt -> CInt))
|
||||
square :: CInt -> CInt
|
||||
square x = x * x
|
||||
|
||||
foreign import ccall "runNSApplication" runNSApplication :: IO ()
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "Hello"
|
||||
cSquare <- wrapSquare square
|
||||
swiftAppMain cSquare
|
||||
putStrLn "hello world"
|
||||
runNSApplication
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
DIST_DIR="$(stack path --dist-dir)"
|
||||
|
||||
ln -sf ../"$DIST_DIR"/build/SwiftHaskell/SwiftHaskell build/
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.0 KiB |