2013-08-31 63 views
13

Tôi có một dự án Haskell nhằm tạo ra một số ràng buộc C++. Tôi đã viết trình bao bọc C và biên dịch chúng thành một thư viện liên kết tĩnh độc lập.Kết nối tĩnh một thư viện C với thư viện Haskell

Tôi muốn viết các ràng buộc Haskell để liên kết tĩnh với trình bao bọc C sao cho tôi không phải phân phối trình bao bọc C một cách riêng biệt nhưng dường như tôi không thể làm việc đó và sẽ đánh giá cao một số trợ giúp.

Tôi chỉ định thư viện C làm thư viện bổ sung nhưng bước cabal build của tôi dường như không thêm nó vào lệnh biên dịch.

Tôi đã tạo một dự án nhỏ để minh họa điều này (http://github.com/deech/CPlusPlusBindings).

Nó chứa một lớp C++ nhỏ (https://github.com/deech/CPlusPlusBindings/tree/master/cpp-src), trình bao bọc C (https://github.com/deech/CPlusPlusBindings/tree/master/c-src), thường trình kiểm tra C hoạt động (https://github.com/deech/CPlusPlusBindings/tree/master/c-test) và tệp Haskell (https://github.com/deech/CPlusPlusBindings/blob/master/src/BindingTest.chs).

Thư viện C được thêm vào trong Setup.hs không có trong tệp Cabal vì đó là cách tôi có dự án thực sự của tôi, xây dựng thư viện C bằng cách sử dụng "make" thông qua Cabal ngay trước khi buildf. Tôi đã xác minh rằng ở bước xây dựng phần extraLibs của BuildInfo chứa tên thư viện và extraLibDirs chứa đúng thư mục.

Kết quả của cabal build của tôi là:

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 

Đáng tiếc là không phải biên soạn cũng không phải là bước nối sử dụng thư viện C. Không có cảnh báo hoặc lỗi khác.

Trả lời

10

Để giải quyết vấn đề này, tôi đã phải:

  1. tái liên kết các thư viện Haskell với các tập tin đối tượng bindings C và
  2. sử dụng thẻ ghc-options trong tập tin Cabal tôi để đảm bảo chúng được liên kết trong theo đúng thứ tự.

Tất cả thay đổi đều nằm trong dự án thử nghiệm (http://github.com/deech/CPlusPlusBindings).

Bên dưới quá trình tạo bản lưu trữ mới bao gồm cả đối tượng C và Haskell được giải thích chi tiết và không đơn giản. Sự phức tạp xảy ra vì không có cách nào (như của Cabal 1.16.0.2) để móc vào phần liên kết của quá trình xây dựng.

Đặt cờ trong tệp Cabal là tầm thường nên nó không được mô tả ở đây.

relinking Các Haskell Thư viện

  1. Đặt loại xây dựng để custom bằng cách thêm:

    build-type: custom 
    

    vào file cabal.

  2. Insert tùy chỉnh xây dựng logic bằng cách thay thế các phương pháp main trong Setup.hs với:

    main = defaultMainWithHooks simpleUserHooks { 
           buildHook = myBuildHook, 
           ... 
         } 
    

    này cho quá trình xây dựng thay vì đi với quá trình xây dựng mặc định theo quy định tại simpleUserHooks nó nên sử dụng chức năng myBuildHook đó là được định nghĩa dưới đây. Tương tự, quá trình dọn dẹp được ghi đè bằng hàm tùy chỉnh myCleanHook.

  3. Xác định móc xây dựng. Hook xây dựng này sẽ chạy make trên dòng lệnh để xây dựng C++, và C phần và sau đó sử dụng các tập tin đối tượng C khi tạo liên kết các ràng buộc Haskell.

    Chúng tôi bắt đầu myBuildHook:

    myBuildHook pkg_descr local_bld_info user_hooks bld_flags = do 
    

    bằng cách đầu tiên chạy make không có đối số:

    rawSystemExit normal "make" [] 
    

    Sau đó, thêm vị trí của các tập tin header và thư mục thư viện và thư viện riêng của mình vào hồ sơ PackageDescription và cập nhật LocalBuildInfo với mô tả gói mới:

    let new_pkg_descr = (addLib . addLibDirs . addIncludeDirs $ pkg_descr) 
        new_local_bld_info = local_bld_info {localPkgDescr = new_pkg_descr} 
    

    Trước khi buildHook kích hoạt configureHook lưu trữ thứ tự biên dịch trong khóa compBuildOrder (trật tự xây dựng thành phần) của bản ghi LocalBuildInfo. Chúng ta cần cô lập tòa nhà của thư viện để chúng ta tách biệt việc xây dựng thư viện và các phần xây dựng thực thi của quá trình xây dựng.

    Việc xây dựng trật tự chỉ là một danh sách và chúng ta đã biết thành phần xây dựng một thư viện nếu nó chỉ là một đồng bằng CLibName loại nhà xây dựng vì vậy chúng tôi cô lập những yếu tố từ danh sách và cập nhật các bản ghi LocalBuildInfo chỉ với họ:

    let (libs, nonlibs) = partition 
             (\c -> case c of 
               CLibName -> True 
               _ -> False) 
             (compBuildOrder new_local_bld_info) 
        lib_lbi = new_local_bld_info {compBuildOrder = libs} 
    
  4. Bây giờ chúng ta chạy xây dựng mặc định móc với các hồ sơ được cập nhật:

    buildHook simpleUserHooks new_pkg_descr lib_lbi user_hooks bld_flags 
    
  5. Sau khi đã thực hiện việc xây dựng một kho lưu trữ đã được tạo ra nhưng chúng ta phải tái tạo nó để bao gồm các đối tượng C tạo ra bởi các lệnh make ở bước 1. Vì vậy, chúng tôi lấy một số cài đặt và một danh sách các đường dẫn tập tin đối tượng 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") 
    

    Và rồi tay nó đi để withComponentsLBI có tác dụng trên mỗi thành phần của việc xây dựng. Trong trường hợp này vì chúng ta chỉ xử lý phần thư viện nên chỉ có một thành phần. Cabal cung cấp getHaskellObjects để có được một danh sách các tập tin đối tượng Haskell và createArLibArchive để tạo một kho lưu trữ vì vậy chúng tôi có thể chạy lại mối liên kết:

    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() 
    
  6. Giá trị mặc định buildHook được chạy trong Bước 4 tạo ra một tập tin cơ sở dữ liệu gói tạm thời có tên là "package.conf.inplace" chứa mô tả của thư viện được xây dựng để tệp thực thi có thể liên kết với nó mà không cần thư viện được cài đặt vào tệp gói hệ thống mặc định.Đáng tiếc là mỗi buildHook chạy khoảng trống nó ra vì vậy chúng tôi cần phải giữ cho một bản sao tạm thời:

    let distPref = fromFlag (buildDistPref bld_flags) 
         dbFile = distPref </> "package.conf.inplace" 
    (tempFilePath, tempFileHandle) <- openTempFile distPref "package.conf" 
    hClose tempFileHandle 
    copyFile dbFile tempFilePath 
    
  7. Bây giờ chúng ta lưu trữ một đường dẫn đến bản sao đó vào cấu trúc LocalBuildInfo cùng với các bộ phận thực thi của quá trình xây dựng mà đã được lọc ra trong Bước 3.

    let exe_lbi = new_local_bld_info { 
            withPackageDB = withPackageDB 
                new_local_bld_info ++ 
                [SpecificPackageDB tempFilePath], 
            compBuildOrder = nonlibs 
           } 
    

    và lưu trữ các con đường một lần nữa trong extraTmpFiles phần của PackageDescription để nó có thể được gỡ bỏ bởi mặc định móc dọn dẹp.

    exe_pkg_descr = new_pkg_descr {extraTmpFiles = extraTmpFiles new_pkg_descr ++ [tempFilePath]} 
    
  8. Bây giờ chúng ta cuối cùng đã chạy mặc định buildHook lại với các hồ sơ được cập nhật (mà bây giờ biết về các kho lưu trữ mới) trên chỉ là các thành phần thực thi:

    buildHook simpleUserHooks exe_pkg_descr exe_lbi user_hooks bld_flags