ocaml / ocaml/dune

confused by Dune foreign_libraries doc: how to define a C library used by C stubs?

Open
#4,409 16 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

c-bindings docs question
Dominant language
OCaml
Stars
1.9k
Forks
500
Avg merge
15h 21m
Merged PRs (30d)
277

Description

I have a simple project that consists of:

  • a simple C library foo
  • an OCaml program prog that uses C stubs that themselves use functions provided by foo

It took me several attempts, and many confused looks at the Dune documentation for dealing with foreign libraries, to get something working. And then, a few weeks later, I realized that the thing was not working (whether it builds or not depends on parallelization choices). Then I went to read the doc on dealing with foreign libraries again, and I got a new solution that was not working. And then another solution that seems to be working, but seems unnecessarily redundant. What is going on?

Project layout (see the complete repro case at https://gitlab.com/gasche-snippets/dune-c-library-repro-case ):

lib-foo/
  foo.c
  foo.h
  dune
prog/
  prog.ml  // uses a function from prog_stubs.c
  prog_stubs.c // includes "../lib-foo/foo.h" and use function from there
  dune
dune

First iteration: declare the library with foreign_library, declare the dependency with foreign_archives

My first iteration declares a foreign_library for foo in lib-foo/dune:

; lib-foo/dune
(foreign_library
 (archive_name foo)
 (language c)
 (names foo)
)

and then uses a foreign_archives stanza to declare that prog depends on foo:

; prog/dune
(executable
 (name prog)
 (foreign_stubs (language c)
   (names prog_stubs))
 (foreign_archives ../lib-foo/foo)
)

This seems to work (it prints 42 as expected):

$ dune clean
$ dune build @all
$ dune exec prog/prog.exe
42

But in fact it does not work:

    $ dune clean
    $ dune exec prog/prog.exe
             gcc prog/prog_stubs.o (exit 1)
    (cd _build/default/prog && /usr/lib64/ccache/gcc -O2 -fno-strict-aliasing -fwrapv -fPIC -D_FILE_OFFSET_BITS=64 -D_REENTRANT -O2 -fno-strict-aliasing -fwrapv -fPIC -g -I /home/gasche/.opam/4.12.0/lib/ocaml -o prog_stubs.o -c prog_stubs.c)
    prog_stubs.c:2:10: fatal error: ../lib-foo/foo.h: No such file or directory
        2 | #include "../lib-foo/foo.h"
          |          ^~~~~~~~~~~~~~~~~~
    compilation terminated.

It looks like Dune does not understand (?) that prog depends on foo.

Reproduction repository: https://gitlab.com/gasche-snippets/dune-c-library-repro-case/-/tree/first-iteration

Second iteration: declare the library with both foreign_library and library, declare the dependency with library

Hidden of the middle of some documentation on foreign build sandboxing (which is clearly not what I'm doing here), there is this suggestion:

The last step is to attach these archives to an OCaml library as follows:

(library
(name bar)
(foreign_archives foo))

Then, whenever you use the bar library, you will also be able to use C functions from libfoo.

I thought that maybe I needed this: maybe depending on bar in this way, I "will be able to use C functions from libfoo". So I did just that: in lib-foo/dune, define both a foreign_library and a library, and in prog/dune, use (libraries foo) instead of (foreign_archives ../foo/foo).

But again this does not work:

$ dune clean
$ dune exec prog/prog.exe
    ocamlopt prog/prog.exe (exit 2)
(cd _build/default && /home/gasche/.opam/4.12.0/bin/ocamlopt.opt -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -g -o prog/prog.exe lib-foo/foo.cmxa -I lib-foo prog/prog_stubs.o prog/.prog.eobjs/native/dune__exe__Prog.cmx)
/usr/bin/ld: prog/prog_stubs.o: in function `call_foo':
/tmp/repro/_build/default/prog/prog_stubs.c:5: undefined reference to `foo'
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

It looks like the issue is that dune detects foo as a dependency, but does not link it into the resulting library. (This may come from the fact that foo is used in C files, but not explicitly in the OCaml code.)

Several Dune issues have discussed the difficulty to basically do what -linkpkg does in ocamlfind (Discuss: dune problems using dynlink plugins, #3141, #2417). But as far as I can tell, none of the solutions proposed in the dynlink case give an easy way to tell dune that prog/prog.exe should link with the foo library.

Reproduction repository: https://gitlab.com/gasche-snippets/dune-c-library-repro-case/-/tree/second-iteration

Third iteration: declare the library with foreign_library and library, declare the dependency with foreign_archives and libraries

Declaring the dependency by using both (libraries foo) and (foreign_archives ../lib-foo/foo) in prog/dune seems to work.

; lib-foo/dune
(library
  (name foo)
  (foreign_archives foo)
)

(foreign_library
 (archive_name foo)
 (language c)
 (names foo)
)
; prog/dune
(executable
 (name prog)
 (foreign_stubs (language c)
   (names prog_stubs))
 (libraries foo)
 (foreign_archives ../lib-foo/foo)
)

Questions:

  • does it, in fact, work?
  • is it the recommended approach?
  • if so, could it be documented more clearly?
  • why is the redundancy (on both ends) necessary?

Reproduction repository: https://gitlab.com/gasche-snippets/dune-c-library-repro-case/-/tree/third-iteration

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the foreign-code.html documentation and the three iteration directories in the linked dune-c-library-repro-case repository. Reproduce the clean-build failures and compare the foreign_library, foreign_archives, and library declarations. Done means the recommended configuration and the reason for any redundancy are clearly documented and consistent with the reproduction case.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, ocaml
Domain
build-system, documentation
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.