静态链接C库和Haskell库
我有一个Haskell项目,旨在创建一些C ++绑定。 我编写了C包装器并将它们编译成一个独立的静态链接库。
我想编写Haskell绑定以静态链接到C包装器,这样我就不必单独分发C包装器,但我似乎无法使它工作,并希望得到一些帮助。
我指定C库作为额外的库,但我的cabal build
步骤似乎没有将它添加到编译命令。
我已经创建了一个小项目来说明这一点( http://github.com/deech/CPlusPlusBindings )。
它包含一个小的C ++类( https://github.com/deech/CPlusPlusBindings/tree/master/cpp-src),C包装器( https://github.com/deech/CPlusPlusBindings/tree/master/c- src ),一个工作的C测试例程( https://github.com/deech/CPlusPlusBindings/tree/master/c-test )和Haskell文件( https://github.com/deech/CPlusPlusBindings/blob/master/ src / BindingTest.chs )。
在Setup.hs中添加了C库,而不是在Cabal文件中,因为这就是我的实际项目,它是在构建步骤之前使用“make”通过Cabal构建C库的。 我已经validation在构建步骤中, extraLibs
部分包含库名称, extraLibDirs
包含正确的目录。
我的cabal build
的输出是:
creating dist/setup ./dist/setup/setup build --verbose=2 creating dist/build creating dist/build/autogen Building CPlusPlusBinding-0.1.0.0... Preprocessing library CPlusPlusBinding-0.1.0.0... Building library... creating dist/build /usr/local/bin/ghc --make -fbuilding-cabal-package -O -odir dist/build -hidir dist/build -stubdir dist/build -i -idist/build -isrc -idist/build/autogen -Idist/build/autogen -Idist/build -I/home/deech/Old/Haskell/CPlusPlusBinding/c-src -I/home/deech/Old/Haskell/CPlusPlusBinding/cpp-includes -optP-include -optPdist/build/autogen/cabal_macros.h -package-name CPlusPlusBinding-0.1.0.0 -hide-all-packages -package-db dist/package.conf.inplace -package-id base-4.6.0.1-8aa5d403c45ea59dcd2c39f123e27d57 -XHaskell98 -XForeignFunctionInterface BindingTest Linking... /usr/bin/ar -r dist/build/libHSCPlusPlusBinding-0.1.0.0.a dist/build/BindingTest.o /usr/bin/ar: creating dist/build/libHSCPlusPlusBinding-0.1.0.0.a /usr/bin/ld -x --hash-size=31 --reduce-memory-overheads -r -o dist/build/HSCPlusPlusBinding-0.1.0.0.o dist/build/BindingTest.o In-place registering CPlusPlusBinding-0.1.0.0... /usr/local/bin/ghc-pkg update - --global --user --package-db=dist/package.conf.inplace
不幸的是,编译和链接步骤都不使用C库。 没有其他警告或错误。
要解决这个问题,我不得不:
- 将Haskell库与C绑定的目标文件重新链接
- 使用我的Cabal文件中的
ghc-options
标记确保它们以正确的顺序链接。
所有更改都在测试项目中( http://github.com/deech/CPlusPlusBindings )。
下面详细解释了创建包含C和Haskell对象的新存档的过程,这并不简单。 之所以出现这种复杂性是因为没有办法(从Cabal 1.16.0.2开始)挂钩构建过程的链接器部分。
设置Cabal文件中的标志是微不足道的,因此这里不再描述。
重新调整Haskell库
-
通过添加以下内容将构建类型设置为
custom
:build-type: custom
到cabal文件。
-
通过将
Setup.hs
的main
方法替换为以下内容来插入自定义构建逻辑:main = defaultMainWithHooks simpleUserHooks { buildHook = myBuildHook, ... }
这告诉构建过程,它应该使用下面定义的
myBuildHook
函数,而不是使用myBuildHook
中定义的默认构建过程。 同样,使用自定义函数myCleanHook
覆盖清理过程。 -
定义构建钩子。 这个构建钩子将在命令行上运行
make
来构建C ++和C部分,然后在创建链接Haskell绑定时使用C对象文件。我们从
myBuildHook
开始:myBuildHook pkg_descr local_bld_info user_hooks bld_flags = do
首先运行没有参数的
make
:rawSystemExit normal "make" []
然后将头文件和库目录的位置以及库本身添加到PackageDescription记录中,并使用新的包描述更新LocalBuildInfo :
let new_pkg_descr = (addLib . addLibDirs . addIncludeDirs $ pkg_descr) new_local_bld_info = local_bld_info {localPkgDescr = new_pkg_descr}
在
buildHook
触发之前,buildHook
在LocalBuildInfo
记录的compBuildOrder
(组件构建顺序)键中存储了编译顺序。 我们需要隔离库的构建,以便我们分离构建过程的库构建和可执行构建部分。构建顺序只是一个列表,我们知道构建组件是一个库,如果它只是一个普通的
CLibName
类型构造函数,所以我们从列表中隔离这些元素并仅用它们更新LocalBuildInfo
记录:let (libs, nonlibs) = partition (\c -> case c of CLibName -> True _ -> False) (compBuildOrder new_local_bld_info) lib_lbi = new_local_bld_info {compBuildOrder = libs}
-
现在我们使用更新的记录运行默认构建挂钩:
buildHook simpleUserHooks new_pkg_descr lib_lbi user_hooks bld_flags
-
一旦完成构建已创建存档,但我们必须重新创建它以包含步骤1中
make
命令生成的C对象。因此,我们获取一些设置和C对象文件路径列表:let verbosity = fromFlag (buildVerbosity bld_flags) info verbosity "Relinking archive ..." let pref = buildDir local_bld_info verbosity = fromFlag (buildVerbosity bld_flags) cobjs <- getLibDirContents >>= return . map (\f -> combine clibdir f) . filter (\f -> takeExtension f == ".o")
然后将其交给
withComponentsLBI
,它作用于构建的每个组件。 在这种情况下,因为我们只处理库部分,所以只有一个组件。Cabal
提供getHaskellObjects
用于获取Haskell对象文件的列表,createArLibArchive
用于创建存档,因此我们可以重新运行链接器:withComponentsLBI pkg_descr local_bld_info $ \comp clbi -> case comp of (CLib lib) -> do hobjs <- getHaskellObjects lib local_bld_info pref objExtension True let staticObjectFiles = hobjs ++ cobjs (arProg, _) <- requireProgram verbosity arProgram (withPrograms local_bld_info) let pkgid = packageId pkg_descr vanillaLibFilePath = pref > mkLibName pkgid Ar.createArLibArchive verbosity arProg vanillaLibFilePath staticObjectFiles _ -> return ()
-
在步骤4中运行的默认
buildHook
创建了一个名为“package.conf.inplace”的临时包数据库文件,该文件包含构建的库的描述,以便可执行文件可以链接到它而不需要将库安装到默认值系统包文件。 不幸的是,每个buildHook
运行都会将其buildHook
,所以我们需要保留一个临时副本:let distPref = fromFlag (buildDistPref bld_flags) dbFile = distPref > "package.conf.inplace" (tempFilePath, tempFileHandle) <- openTempFile distPref "package.conf" hClose tempFileHandle copyFile dbFile tempFilePath
-
现在,我们将该副本的路径存储到
LocalBuildInfo
结构中,以及在步骤3中过滤掉的构建过程的可执行部分。let exe_lbi = new_local_bld_info { withPackageDB = withPackageDB new_local_bld_info ++ [SpecificPackageDB tempFilePath], compBuildOrder = nonlibs }
并将路径再次存储在
extraTmpFiles
部分中,以便可以通过默认的清理钩子将其删除。exe_pkg_descr = new_pkg_descr {extraTmpFiles = extraTmpFiles new_pkg_descr ++ [tempFilePath]}
-
现在,我们最终再次使用更新的记录(现在知道新存档)运行默认的
buildHook
:buildHook simpleUserHooks exe_pkg_descr exe_lbi user_hooks bld_flags