适用于iOS的Haskell交叉编译器

到目前为止,我们已经为Raspberry Pi构建了Haskell Cross Compiler,并且为Android构建了Haskell Cross Compiler。 为了解决这个问题,我们还将为iOS构建一个交叉编译器。

WWDC发出信号通知32位设备的末尾,最后32位设备是iPad(第四代)和iPhone 5 / iPhone 5C,我们将只构建64位交叉编译器。 请注意,Apple 在其他地方 arm64 称为 aarch64 这是很不幸的。

SDK和LLVM

Apple随Xcode一起提供了iOS SDK。 因此,需要AppStore的Xcode的最新副本。 由于Apple不随Xcode一起交付optllc ,而且GHC当前要求llvm4提供optllc ,因此我们也需要从LLVM发布下载网站获取副本。

工具链包装

Apple为xcrun实用程序提供了针对我们所需工具的自动设置工具链。 但是,我们仍然需要提供目标前缀别名,以实现更好的自动工具互操作。 最初的设置工作归功于ghc-ios-scripts,我们将使用经过稍微修改的版本:

  #!/ bin / bash 
 名称= $ {0 ## * /} 
cmd = $ {name ## *-}
target = $ {name%-*}
 案例$ name 
*-阴谋)
fcommon =“-builddir = dist / $ {target}”
fcompile =“ --with-ghc = $ {target} -ghc”
fcompile + =“ --with-ghc-pkg = $ {target} -ghc-pkg”
fcompile + =“ --with-gcc = $ {target} -clang”
fcompile + =“ --with-ld = $ {target} -ld”
fcompile + =“ --hsc2hs-options =-交叉编译”
fconfig =“-disable-shared --configure-option =-host = $ {target}”
案例$ 1
configure | install)flags =“ $ {fcommon} $ {fcompile} $ {fconfig}” ;;;
build)flags =“ $ {fcommon} $ {fcompile}” ;;
list | info | update)flags =“” ;;
“”)flags =“” ;;
*)标志= $ fcommon ;;
埃萨克
;;
aarch64-apple-ios-clang | aarch64-apple-ios-ld)
flags =“-sdk iphoneos $ {cmd} -arch arm64”
cmd =“ xcrun”
;;
aarch64-apple-ios- * | aarch64-apple-ios- *)
flags =“-sdk iphoneos $ {cmd}”
cmd =“ xcrun”
;;
x86_64-apple-ios-clang | x86_64-apple-ios-ld)
flags =“-sdk iphonesimulator $ {cmd} -arch x86_64”
cmd =“ xcrun”
;;
x86_64-apple-ios- *)
flags =“-sdk iphonesimulator $ {cmd}”
cmd =“ xcrun”
;;
#默认
* -nm | * -ar | * -ranlib);;
*)echo“未知命令:$ {0 ## * /}”>&2; 1号出口;;
埃萨克
  exec $ cmd $ flags“ $ @” 

通用wrapper可以从zw3rk / toolchain-wrapper存储库中获得。 它不仅包含上面的iOS部分,还包含Raspberry Pi和Android的部分,因此可用于所有三个平台。

同样,我们需要创建目标前缀别名:

 针对“ aarch64-apple-ios x86_64-apple-ios”中的目标; 做 
用于“ clang ld ld.gold nm ar ranlib cabal”中的命令; 做
ln -s包装程序$ target- $ command
完成
完成

zw3rk / toolchain-wrapper存储库中的引导脚本将生成Android,iOS和Raspberry Pi的所有别名。

先决条件

使用PATH的工具链包装器和别名,构建ghc所需的全部就是ghccabal 。 使用自制程序获取ghcllvm-3.7的最新副本应该很容易

 酿造安装ghc llvm@3.7 

llvm@3.7将安装opt-3.7llc-3.7 ,这是引导编译器所需的。 但是,homebrew安装的ghc不带llcopt的版本后缀。 只需更换即可解决

  (“ LLVM llc命令”,“ llc”), 
(“ LLVM opt命令”,“ opt”)

  (“ LLVM llc命令”,“ llc-3.7”), 
(“ LLVM opt命令”,“ opt-3.7”)

settings文件中,如果是来自自制软件的ghc ,则位于

  /usr/local/opt/ghc/lib/ghc-8.0.2/settings 

我们还需要alexhappy ,可以与cabal一起安装

 阴谋集团安装亚历克斯快乐 

正如我们为Raspberry Pi和Android所做的那样,我们还需要针对iOS的libffi

  git clone https://github.com/zw3rk/libffi.git 
  cd libffi 
  ./autogen.sh 
  CC =“ aarch64-apple-ios-clang” \ 
CXX =“ aarch64-apple-ios-clang” \
。/配置 \
--prefix = /路径/到/ libffi / aarch64-apple-ios \
--host = aarch64-apple-ios \
--enable-static =是--enable-shared =是
 进行&&进行安装 
  git clean -f -x -d 
  ./autogen.sh 
  CC =“ x86_64-apple-ios-clang” \ 
CXX =“ x86_64-apple-ios-clang” \
。/配置 \
--prefix = /路径/到/ libffi / x86_64-apple-ios \
--host = x86_64-apple-ios \
--enable-static =是--enable-shared =是
 进行&&进行安装 

注意:我们需要使用 zw3rk / libffi 分支来提供 -ios 支持,直到将 libffi / libffi#307 拉取请求合并到官方libffi存储库中为止。 或者一个新的autoconf版本(最新的2.69版本于2012年发布)已被切断并广泛可用。

建筑GHC

使用llvm4, alex optllcPATH happy

 导出PATH = $ HOME / .cabal / bin:$ PATH 
导出PATH = / path / to / llvm4 / bin:$ PATH
导出PATH = / path / to / wrapped-toolchain:$ PATH

从打补丁的my-ghc分支构建GHC

  git clone-递归git://git.haskell.org/ghc.git 
光盘ghc
git remote add zw3rk https://github.com/zw3rk/ghc.git
git fetch zw3rk
git checkout zw3rk / my-ghc -b my-ghc
git子模块更新--init --recursive

应该只需要

  #设置路径 
导出PREFIX = / my / prefix
 导出LIBFFI = /path/to/libffi 
 针对“ aarch64-apple-ios x86_64-apple-ios”中的目标; 做 
#清理构建树
git clean -x -f -d
  #启动构建系统 
./启动
  #配置以$ target为目标的GHC 
./configure --target = $ target \
--prefix = $ PREFIX \
--disable-large-address-space \
--with-system-libffi \
--with-ffi-includes = $ LIBFFI / $ target /include \
/include \
--with-ffi-libraries = $ LIBFFI / $ target / lib
  #创建一个mk / build.mk并将BuildFlavour设置为quick-cross 
sed -E“ s / ^#(BuildFlavour [] + = quick-cross)$ / \ 1 /” \
mk / build.mk.sample> mk / build.mk
  #编译并安装ghc 
进行-j &&进行安装
完成

60-120分钟后,根据您的硬件,在$PREFIX/bin应显示和闪亮的新aarch64-apple-ios-ghcx86_64-apple-ios-ghc

编译Hello World

与我们为Android构建Hello World库的方式相似,我们将构建相同的Hello World库并将其包装到iOS应用程序中。

具有以下代码的Lib.hs

 模块库在哪里 
 导入Foreign.C(CString,newCString) 
  -| 将haskell函数@ chello @导出为@ hello @。 
国外出口ccall“你好” chello :: IO CString
  -| 小包装器返回CString 
chello = newCString你好
  -| 原始的haskell功能。 
你好=“你好,来自Haskell”

导出chello C函数,我们将在iOS应用程序中调用该函数以从Haskell获取C字符串。 没什么令人兴奋的,但是它将演示基本的互操作性。

使用Swift和Xcode创建一个简单的iOS 单视图应用程序应该为我们的Hello World应用程序提供必要的应用程序模板。 使用Objective-C将使调用 chello 更加容易。 但是,由于苹果现在已经推动swift一段时间了,我们将使用swift。

我们将需要一个桥接标头,以使C原型迅速发展。 最简单的方法是将一个新的目标c文件添加到项目中,例如tmp.m ,这反过来将导致Xcode询问是否应创建桥接标头。 是,然后删除tmp.h 将所需的原型添加到helloworld-Bridging-Header.h

  extern void hs_init(int * argc,char ** argv []); 
extern char * hello();

AppDelegate.swift ,当应用程序完成启动时,我们将调用hs_init

  func application(_ application:UIApplication, 
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey:Any]?)->布尔{
//应用程序启动后进行自定义的替代点。
hs_init(nil,nil)
返回真
}

Label添加到Main.storyboard ,将IBOutlet连接到ViewController.swift ,并将其text属性设置为hello的结果

 类ViewController:UIViewController { 
@IBOutlet弱var标签:UILabel!
覆盖func viewDidLoad(){
super.viewDidLoad()
label.text =字符串(cString:hello())
}
}

应该足够了。 在实际设备上构建和运行应用程序之前,我们需要构建Haskell库并告诉Xcode对其进行链接。

编译成用于aarch64 (设备)和x86_64 (模拟器)的静态库

  aarch64-apple-ios-ghc -odir arm64 -hidir arm64 \ 
-lffi -L /路径/到/ libffi / aarch64-apple-ios / lib \
-staticlib -o hs-libs / arm64 / libhs.a hs / Lib.hs
  x86_64-apple-ios-ghc -odr x86_64 -hidir x86_64 \ 
-lffi -L /路径/到/ libffi / x86_64-apple-ios / lib \
-staticlib -o hs-libs / x86_64 / libhs.a hs / Lib.hs

并将它们转变为结合了两种架构的通用库

  lipo -create -output hs-libs / libhs.a \ 
hs-libs / arm64 / libhs.a hs-libs / x86_64 / libhs.a

应该在hs-libs文件夹中提供libhs.a静态库。 在Xcode中链接它需要将它与libiconv.tbd一起添加到Xcode的helloworld项目的Build Phases选项卡的Link Binary With Libraries部分中。 由于我们不能使用GHC仅构建位码库,因此我们还需要在“ 构建设置”选项卡中将“ 启用位码 ”设置No。

最终在设备上运行该应用程序应向我们展示