Use C binding with PPX and dune

Hi,
I’m trying to write a ppx-rewriter for my library. The issue is that my library need to get the flags for the C++ bindings I’m using. When I import my library in an example with the following dune, it works:

(executable
  (name main)
  (flags (:standard -w -49
                    -cclib -lstdc++
                    -cclib -lraplcap-msr
                    -cclib -lsmartgauge
                    -cclib -lusb-1.0
                    -cclib -lmammut))
  (libraries tezos_oxymeter))

However, when I try to import it into my ppx, dune says it can’t reach the library. Could this be an error related to the fact that it’s in a ppx ?

This is my dune for the ppx declaration:

(library
  (public_name ppx-tezos_oxymeter)
  (name ppx_oxymeter)
  (kind ppx_rewriter)
  (libraries tezos_oxymeter ppxlib)
  (modules ppx_oxymeter)
  (flags (:standard -w -49 -linkall
                    -cclib -lstdc++
                    -cclib -lraplcap-msr
                    -cclib -lsmartgauge
                    -cclib -lusb-1.0
                    -cclib -lmammut))
  (preprocess (pps ppxlib.metaquot)))

Does someone have ever encountered this problem and find a solution to solve it?

You should provide the error in question, “it can’t reach the library” is a bit vague. In general -cclib and -linkall are used when linking so they should go inside (link_flags ...).

Cheers,
Nicolas

Oh, sorry, I forget to link the error:

dune build lib
    ocamlopt lib/ppx_oxymeter.cmxs (exit 2)
(cd _build/default && /home/user/.opam/default/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 -w -49 -linkall -cclib -lstdc++ -cclib -lraplcap-msr
 -cclib -lsmartgauge -cclib -lusb-1.0 -cclib -lmammut -g -shared
 -linkall -I lib -o lib/ppx_oxymeter.cmxs lib/ppx_oxymeter.cmxa)
/usr/bin/ld: cannot find -lraplcap-msr
/usr/bin/ld: cannot find -lsmartgauge
/usr/bin/ld: cannot find -lusb-1.0
/usr/bin/ld: cannot find -lraplcap-msr
/usr/bin/ld: cannot find -lsmartgauge
/usr/bin/ld: cannot find -lusb-1.0
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

Unfortunately, the (link_flags ...) flag can only be used when you are writing an executable. I’ve tried to replace it with the (library_flags ...) but I still have the same issue.

I think I should do a small example to explain my issue here, it might be clearer.

Suppose I have an OCaml library, ocaml-binding that uses C++ bindings. I’m writing a library, my_lib which works with the binding library. What I’m trying to achieve is that, when I use this my_lib in a project (let’s call it my_project), I don’t to have to specify the C++ linking flags in it.

In this scenario, the structure would be:

.
|- my_lib/
    |- dune
    |- dune-project
    |- [...]
|- ocaml-binding/
   |- [...]
|- my_project/
   |- dune
   |-dune-project
   |- [...]

I’d like to be able to define this dune for my_project:

(<executable or library>
  (libraries my_lib))

whereas actually, what I have to do is:

(executable or library
  (name my_project)
  (libraries my_lib)
  (<link_flags or library_flags> -cclib binding))

I’ve tried to insert the flags into the my_lib dune definition to force it to embed the library into the .cmx:

(library
   (name my_lib)
   (libraries ocaml-binding)
   (library_flags -linkall -cclib binding))

However, it displays the error above about the messing c++ libraries.

Solving this using would solve my ppx issue because the error comes from the same point, the inability to get the c++ embedded library.

I don’t know the point I missed, to tell dune to embed everything.

My advice for these kind of issues would be to try with the simplest uttermost trivial example you can come up that has all the features you are interested. Then try to compile it “by hand” with the compiler (not dune) to make sure that you understand the needed command-line invocations. Once you can make it work by hand you should be able to get dune to do what you need.

If you want more concrete help I would suggest you post a minimal reproduction of what you are trying to do in full (not pseudo-code), together with the error messages or description of the behaviour you are getting and the behaviour that would like. Right now it is too vague (for me at least) to try to help you.

Cheers,
Nicolás

1 Like

Thank you very much for your advices @nobj, I will try to use the native compiler to see if I can make it work, as you suggested!

I will also produce a minimal reproduction here as your second suggestion advice :slight_smile:

I come here with a better understanding of my issue but still no way to solve it after reading forums, documentations and try different options (even the minimal command line example).

Context:

I’m using this library for my internship, ocaml_mammut. This is a C++ binding to this library, mammut.
The dune file for the ocaml-mammut is:

(library
 (name mammut)
 (public_name mammut)
 (flags (:standard -w -49 -linkall ))
 (modules mammut)
 (libraries ctypes.stubs mammut.generated threads)
 (c_library_flags -lstdc++)
)

The mammut.generated is here to compile the mammut c++ library and generate the c++ stubs. Its dune is this one (a bit long sorry):

(library
 (name mammut_generated)
 (public_name mammut.generated)
 (wrapped false)
 (modules mammut_generated)
 (libraries ctypes.stubs mammut_stubs_description)
 (foreign_stubs
  (language c)
  (names mammut_stubs)
  (include_dirs ../../vendor/mammut/include))
 (foreign_archives raplcap-msr mammut usb-1.0 smartgauge)
 (c_library_flags -lstdc++)
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(rule
 (targets libraplcap-msr.a dllraplcap-msr%{ext_dll})
 (deps (source_tree ../../vendor))
 (action (progn
  (chdir ../../vendor/raplcap (progn
   (bash
    "cmake -B_build/shared -DBUILD_SHARED_LIBS=ON .")
   (bash
    "cmake --build _build/shared")))
   (bash
    "cp ../../vendor/raplcap/_build/shared/msr/libraplcap-msr.so dllraplcap-msr.so")
  (chdir ../../vendor/raplcap (progn
   (bash
    "cmake -B_build/static .")
   (bash
    "cmake --build _build/static")))
   (bash
    "cp ../../vendor/raplcap/_build/static/msr/libraplcap-msr.a .")
)))

; The vendored libmammut.
(data_only_dirs vendor)

(rule
 (targets libmammut.a dllmammut%{ext_dll} libusb-1.0.a  dllusb-1.0%{ext_dll} libsmartgauge.a dllsmartgauge%{ext_dll})
 (deps (source_tree ../../vendor))
 (action (progn
  (chdir ../../vendor/mammut (progn
   (bash
    "cmake .")
   (bash
    "make")))
  (bash
   "cp ../../vendor/mammut/src/libmammut_static.a libmammut.a")
  (bash
   "cp ../../vendor/mammut/src/libmammut.so dllmammut.so")
  (bash
   "cp ../../vendor/mammut/src/external/libusb-1.0.9/libusb/.libs/libusb-1.0.a .")
  (bash
   "cp ../../vendor/mammut/src/external/libusb-1.0.9/libusb/.libs/libusb-1.0.so dllusb-1.0.so")
  (bash
   "cp ../../vendor/mammut/src/external/odroid-smartpower-linux/libsmartgauge.a .")
  (bash
   "cp ../../vendor/mammut/src/external/odroid-smartpower-linux/libsmartgauge.so dllsmartgauge.so")
  )))

;;;;;;;;;;;;;;;;;;


; Type bindings.
(library
 (name generate_types)
 (public_name mammut.generated_type_descriptions)
 (modules generate_types)
 (libraries ctypes.stubs))

(executable
 (name generate_types_start)
 (modules generate_types_start)
 (libraries ctypes.stubs generate_types))

(rule
 (with-stdout-to mammut_generated.types.c
  (run ./generate_types_start.exe)))

; Based partially on
;   https://github.com/avsm/ocaml-yaml/blob/master/types/stubgen/jbuild#L20
(rule
 (targets mammut_types.exe)
 (deps (:c mammut_generated.types.c))
 (action (bash "\
  %{cc} %{c} \
  -I '%{lib:ctypes:.}' \
  -I %{ocaml_where} \
  -I ../../vendor/mammut/include -o %{targets}")))

(rule
 (with-stdout-to mammut_types.ml
  (run ./mammut_types.exe)))

; Function bindings.
(library
 (name mammut_stubs_description)
 (public_name mammut.generated_function_descriptions)
 (flags (:standard -w -9-16-27))
 (wrapped false)
 (modules mammut_types mammut_stubs_description)
 (libraries ctypes.stubs generate_types))

(executable
 (name generate_c_functions)
 (modules generate_c_functions)
 (libraries ctypes.stubs mammut_stubs_description))

(executable
 (name generate_ml_functions)
 (modules generate_ml_functions)
 (libraries ctypes.stubs mammut_stubs_description))

(rule
 (with-stdout-to mammut_generated.ml
  (run ./generate_ml_functions.exe)))

(rule
 (with-stdout-to mammut_stubs.c
  (run ./generate_c_functions.exe)))

Attempts:

In the current state of ocaml-mammut, when you use it into another project, you have to specify the flags with (flags (-cclib -lraplcap-msr -cclib -lsmartgauge -cclib -lusb-1.0 -cclib -lmammut)) in the project itself. However, according to the manual and this discussion I had with @ivg, if I transform the ocaml-mammut dune in this way:

(library
 (name mammut)
 (public_name mammut)
 (flags (:standard -w -49 -linkall
                    -cclib generated
                    -cclib -lraplcap-msr
                    -cclib -lsmartgauge
                    -cclib -lusb-1.0
                    -cclib -lmammut ))
 (modules mammut)
 (libraries ctypes.stubs mammut.generated threads)
 (c_library_flags -lstdc++)
)

I’m not supposed to have to write the (flags ...) stanza in other projects any more because it should be a standalone library.

Issue:

When I use the previous dune, it compiles, I get the c++ .a libraries at the right place. However, when I reach the link point, it fails with:

ocamlopt src/mammut.cmxs (exit 2)
(cd _build/default && /home/etienne/.opam/default/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 -w -49 -linkall -cclib -lstdc++
-cclib -lraplcap-msr
-cclib -lsmartgauge -cclib -lusb-1.0 -cclib 
-lmammut -g -shared -linkall -I src -o src/mammut.cmxs src/mammut.cmxa)
/usr/bin/ld: cannot find -lraplcap-msr
/usr/bin/ld: cannot find -lsmartgauge
/usr/bin/ld: cannot find -lusb-1.0
/usr/bin/ld: cannot find -lraplcap-msr
/usr/bin/ld: cannot find -lsmartgauge
/usr/bin/ld: cannot find -lusb-1.0
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

I don’t understand why dune can’t find the .a and add them in the cmxa whereas there are in the _build repository.

Well, because nobody asked dune to do this. Dune just follows your instructions and the instructions of the mammut bindings packager. Indeed, the right way of packing the bindings would be including the code of the bound library in the provided package (in .cmxs, .cmxa, and .a files) and dune will magically handle everything for you so you would never ever even known about what is happening underneath the hood.

The truth is that the ocaml-mammut library is plain broken. And the right solution would be to fix the upstream.

You can still try to fix things on your side. For that we need to understand what the error message is saying to us. In fact, the error message that you have is from the system linker that is unable to find the library code. It is already has nothing to do with dune or ocaml. It is assumed that those libraries should be installed as system dependencies and properly configured with your system tools (e.g., ldconfig). So make sure that you have them installed, e.g., sudo apt install libusb-dev. If you have some libraries installed manually you need to either install it in the default paths, so that ldconfig -p shows it, e.g.,

$ ldconfig -p | grep libusb-1
	libusb-1.0.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libusb-1.0.so.0

Thanks, @ivg, for your answer! I’m sorry to bother you with that again: it’s a blocking point for the internship I’m working on. That’s why I’m trying to fix the library itself, as it has first been made as an experimental tool.

This is the part I don’t understand: on the opamify branch, in the vendor directory, there is the submodule that contains the mammut source code. And the mammut.generated contains the local .a files in the build directory because it builds them from the vendor dir. I don’t see this issue with it. Isn’t dune supposed to link them all?

I agree with you, and I’m trying to fix, but it’s my first time with c++ bindings…

Indeed, I didn’t think of it as a temporary way to fix it! The fix allows me to work it the time I figure out how to correct the build on the main repository.

Oh, don’t be sorry. Troubles are happening and we’re here to help you)

Hmm, I looked at this branch and it looks fine. Dune should pack the code correctly. What is probably happening, is that downstream, in your application, you still have the old workaround that was trying to fix the missing libraries, e.g., the following,

(flags (:standard -w -49 -linkall
                    -cclib generated
                    -cclib -lraplcap-msr
                    -cclib -lsmartgauge
                    -cclib -lusb-1.0
                    -cclib -lmammut ))

It is no longer needed (in fact it requires dune to link your code with external (system) libraries, but now your ocaml-mamut package already ships all the necessary code). So make sure it is removed. And nowhere in your code you have those -cclib -lXXX flags anymore).

Thanks so much!

Do you mean that I don’t have to add the (flags ...) in the ocaml_mammut dune file as I was trying to do or that I don’t need the (flags …)in the library I'm writing and that usesocaml_mammut`?

Because if I use the ocaml_mammut as it is on the opamify branch, I have to put the (flags ...) when I try to build an executable type with it. And, if I try to add the flags in the ocaml_mammut dune file, dune gives me the previous error, saying it can’t find the .a (even if after the failing build, the .a appears in the _build directory :confused:

Yeah, that’s the crux of the problem and those flags were just hiding it. Basically, dune fails to find the .a file and link the code, and those flags just let it go forward as they basically say “the libraries will be provided later by the system”. So you need to figure out why the .a files are not found. Can you give a link to a reproducible example of the failing build?

The interesting point is that on my library when I’m running the example and putting the good flag, this time it works. However, as long they are no longer here, it fails. So, it means it’s able to retrieve it but not the flags part.

If you want to reproduce the build for mammut, I’ve made a new branch on my fork where to put my dirty work to try to find a solution. It’s on the upgrade_me branch here. To do the failing build, you have to do:

opam switch create . --deps-only
eval $(opam env)
make

For the integration into my library, I’ve made a special branch, reproduce_build, where you have to do:

opam switch create . --deps-only
eval $(opam env)
dune build lib
dune exec example/main.exe

It will build the switch in _opam, build the library and run the example (located in example/). On this branch, I’ve commented the (flags ...) to trigger the error.