How to debug linking errors and module API conflicts

I’m at the stage in my OCaml journey where I think I should be able to troubleshoot linking problems and module conflicts, but they are still pretty much an opaque mystery to me. I was hoping some(ones) might help me untangle my current knot, and perhaps in a way that helps teach how to escape such binds in principle.

In this particular case, I am hitting an unbuildable project due to a conflict between the mrmime and gapi-ocaml packages. It’s clear that they depend on conflicting Base64 modules, but I don’t know how to dig deeper in resolve this (perhaps by way of a shim, or patch, or PR, or whatever).

Here is a minimal reproducing case:

Assuming you’ve installed the two packages:

opam install mrmime gapi-ocaml

in ./_build:

cat: _build: Is a directory

in ./dune:

(executable
 (public_name main)
 (libraries threads
            mrmime
            gapi-ocaml))

in ./dune-project:

(lang dune 2.5)

in ./main.ml:

let _ = Mrmime.Mail.mail
let _ = GapiGmailV1Model.Message.empty

let () = print_endline "foo"

in ./main.opam:

Then run

dune build
    ocamlopt main.exe (exit 2)
(cd _build/default && /home/sf/.opam/4.10.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 main.exe /home/sf/.opam/4.10.0/lib/ocaml/unix.cmxa -I /home/sf/.opam/4.10.0/lib/ocaml /home/sf/.opam/4.10.0/lib/ocaml/threads/threads.cmxa -I /home/sf/.opam/4.10.0/lib/ocaml /home/sf/.opam/4.10.0/lib/mrmime/butils/butils.cmxa -I /home/sf/.opam/4.10.0/lib/mrmime/butils /home/sf/.opam/4.10.0/lib/stdlib-shims/stdlib_shims.cmxa /home/sf/.opam/4.10.0/lib/fmt/fmt.cmxa /home/sf/.opam/4.10.0/lib/bigarray-compat/bigarray_compat.cmxa /home/sf/.opam/4.10.0/lib/ke/ke.cmxa /home/sf/.opam/4.10.0/lib/bigstringaf/bigstringaf.cmxa -I /home/sf/.opam/4.10.0/lib/bigstringaf /home/sf/.opam/4.10.0/lib/mrmime/encoder/encoder.cmxa /home/sf/.opam/4.10.0/lib/result/result.cmxa /home/sf/.opam/4.10.0/lib/ptime/ptime.cmxa /home/sf/.opam/4.10.0/lib/macaddr/macaddr.cmxa /home/sf/.opam/4.10.0/lib/astring/astring.cmxa /home/sf/.opam/4.10.0/lib/domain-name/domain_name.cmxa /home/sf/.opam/4.10.0/lib/ipaddr/ipaddr.cmxa /home/sf/.opam/4.10.0/lib/base64/base64.cmxa /home/sf/.opam/4.10.0/lib/base64/rfc2045/base64_rfc2045.cmxa /home/sf/.opam/4.10.0/lib/uutf/uutf.cmxa /home/sf/.opam/4.10.0/lib/uuuu/uuuu.cmxa /home/sf/.opam/4.10.0/lib/yuscii/yuscii.cmxa /home/sf/.opam/4.10.0/lib/coin/coin.cmxa /home/sf/.opam/4.10.0/lib/rosetta/rosetta.cmxa /home/sf/.opam/4.10.0/lib/pecu/pecu.cmxa /home/sf/.opam/4.10.0/lib/rresult/rresult.cmxa /home/sf/.opam/4.10.0/lib/angstrom/angstrom.cmxa /home/sf/.opam/4.10.0/lib/mrmime/mrmime.cmxa /home/sf/.opam/4.10.0/lib/extlib/extLib.cmxa /home/sf/.opam/4.10.0/lib/ocaml/bigarray.cmxa -I /home/sf/.opam/4.10.0/lib/ocaml /home/sf/.opam/4.10.0/lib/netsys/netsys_oothr_mt.cmxa /home/sf/.opam/4.10.0/lib/netsys/netsys_oothr_mt_init.cmx /home/sf/.opam/4.10.0/lib/netsys/netsys.cmxa -I /home/sf/.opam/4.10.0/lib/netsys /home/sf/.opam/4.10.0/lib/ocaml/str.cmxa -I /home/sf/.opam/4.10.0/lib/ocaml /home/sf/.opam/4.10.0/lib/netstring/netstring.cmxa -I /home/sf/.opam/4.10.0/lib/netstring /home/sf/.opam/4.10.0/lib/curl/curl.cmxa -I /home/sf/.opam/4.10.0/lib/curl /home/sf/.opam/4.10.0/lib/zarith/zarith.cmxa -I /home/sf/.opam/4.10.0/lib/zarith /home/sf/.opam/4.10.0/lib/cryptokit/cryptokit.cmxa -I /home/sf/.opam/4.10.0/lib/cryptokit /home/sf/.opam/4.10.0/lib/easy-format/easy_format.cmxa /home/sf/.opam/4.10.0/lib/biniou/biniou.cmxa /home/sf/.opam/4.10.0/lib/yojson/yojson.cmxa /home/sf/.opam/4.10.0/lib/gapi-ocaml/gapi_ocaml.cmxa .main.eobjs/native/dune__exe__Main.cmx)
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__data_begin':
:(.data+0x0): multiple definition of `camlBase64__data_begin'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.data+0x0): first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__$2f$2f_85':
/home/sf/.opam/4.10.0/.opam-switch/build/base64.3.4.0/_build/default/src/base64.ml:28: multiple definition of `camlBase64__code_begin'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o):/home/sf/.opam/4.10.0/.opam-switch/build/extlib.1.7.7/src/base64.ml:39: first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64':
:(.data+0x2e0): multiple definition of `camlBase64'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.data+0x1a0): first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__gc_roots':
:(.data+0x3a8): multiple definition of `camlBase64__gc_roots'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.data+0x1f8): first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__entry':
/home/sf/.opam/4.10.0/.opam-switch/build/base64.3.4.0/_build/default/src/base64.ml:280: multiple definition of `camlBase64__entry'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o):/home/sf/.opam/4.10.0/.opam-switch/build/extlib.1.7.7/src/base64.ml:128: first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__code_end':
:(.text+0x181c): multiple definition of `camlBase64__code_end'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.text+0x99d): first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__data_end':
:(.data+0x658): multiple definition of `camlBase64__data_end'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.data+0x458): first defined here
/usr/bin/ld: /home/sf/.opam/4.10.0/lib/base64/base64.a(base64.o): in function `camlBase64__frametable':
:(.data+0x660): multiple definition of `camlBase64__frametable'; /home/sf/.opam/4.10.0/lib/extlib/extLib.a(base64.o)::(.data+0x460): first defined here
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking

What should be my first step in figuring out what’s responsible for this conflict? What are my options in resolving it?

It is actually all there if you squint hard enough : )

I think it is due to both the base64 and extLib defining a module called Base64. You need to let go of one of them.

Best wishes,
Nicolás

1 Like

Thank @nojb. This helps clarify and narrow down the conflict a bit. I’m not requiring either of these directly, So, assuming a user like myself (with some help :wink: ) has identified which modules are conflicting, is there a standard way of figuring out which dependencies introduce the conflict? In this case, it is fairly easy, as I only have two deps, so I can run something like

[sf@comp leila]$ opam info mrmime | rg base64\|extlib
              "base64" {>= "3.1.0"}
[sf@comp leila]$ opam info gapi-ocaml | rg base64\|extlib
              ("extlib" | "extlib-compat")

but with a longer dependency chain, this could be burried in a non obvious way. How would one diagnose the packages introducing the conflicting modules in such a case?

More important, what can be done to make this kind of situation better, for myself and for other users? E.g., should the base64 and exlib packages both include a conflicts field in their opam manifests to help users detect this conflict earlier? Would that be a helpful PR?

What if I had to make these packages work together? Iiuc, this is just a nominal conflict, and if extlib didn’t dump it’s modules into the top-level, then the problem wouldn’t exist at all. So perhaps I should consider a patch, or vendoring one or the other package to fix this problem? Is that a reasonable course of action? If not, what other approaches do serious OCaml hackers take?

One thing to note, is that opam isn’t responsible for linking: that’s handled by dune and ocamlfind/findlib. When I have problems like this, I always take the ocamlfind link command, and find out what the list of libraries is, that it’s trying to link. Then I can figure out if there’s a conflict: at least once I recall using ocamlobjinfo to discover which the exported symbols of each library were, and thereby narrowing it down … but I might be misremembering.

Your situation is analogous to a C programmer finding weird conflicts between DLLs: the tools are there to figure out the conflict, but the first step is get down to the list of DLLs.

I could be unfair here, but … dune hides that information pretty effectively (the logfile is … no easy to decipher). OTOH, the information -is- there in the logfile.

1 Like

Thank @nojb. This helps clarify and narrow down the conflict a bit. I’m not requiring either of these directly, So, assuming a user like myself (with some help :wink: ) has identified which modules are conflicting, is there a standard way of figuring out which dependencies introduce the conflict?

Well, if you read the linking error, you can see that the repeated symbol camlBase64__*, is mentioned to appear in both .../base64/base64.* and .../exitlib/extLib.*. That should give it to you.

More important, what can be done to make this kind of situation better, for myself and for other users? E.g., should the base64 and exlib packages both include a conflicts field in their opam manifests to help users detect this conflict earlier? Would that be a helpful PR?

I don’t think so, because there is no problem having both installed in your opam universe. It is only a problem if you try to use both at the same time.

What if I had to make these packages work together? Iiuc, this is just a nominal conflict, and if extlib didn’t dump it’s modules into the top-level, then the problem wouldn’t exist at all. So perhaps I should consider a patch, or vendoring one or the other package to fix this problem? Is that a reasonable course of action?

Yes, wrapping the library in a top-level module is the standard way to solve this issue. But old-style (unwrapped) libraries cannot easily change to the new style because it breaks every piece of code that uses them out there. Having said that, dune offers a way to handle the migration in steps by first building both the wrapped and unwrapped libraries with deprecation notice.

Cheers,
Nicolás

1 Like