# Dune problems using Dynlink plugins

**URL:** <https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874>\
**Category:** Learning\
**Tags:** dune\
**Created:** [November 12, 2018, 1:53pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874 "2018-11-12T13:53:28Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![talex5](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/talex5/32/353_2.png) [@talex5](https://discuss.ocaml.org/u/talex5)\
**Post date:** [November 12, 2018, 1:53pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/1 "2018-11-12T13:53:28Z")

</div>

Hi,

I’m trying to migrate an OCamlbuild project to Dune, but I’m having trouble building a plugin (for Dynlink).

The idea is that the plugin adds a GTK UI, using lablgtk. This must not be linked into the main program, because it uses a C library that might not be present on the target system.

I’ve put up a minimal repository showing the problem here:

[https://github.com/talex5/dune-test](https://github.com/talex5/dune-test)

```bash
git clone https://github.com/talex5/dune-test.git
cd dune-test

```

By default, it fails with:

```auto
$ make
dune build @all
dune exec ./myprog.exe
Failed to load GTK GUI plugin: error loading shared library:
/home/user/tmp/dune_plugin/_build/default/plugin/plugin.cmxs:
undefined symbol: camlGtkMain__init_inner_102598

```

That seems to indicate that the lablgtk archive isn’t being linked with the plugin.  
The library rule I’m using is:

```auto
(library
 (name plugin)
 (optional)
 (libraries lablgtk2 lwt_glib))

```

With ocamlfind, I would use `-linkpkg` to fix the error.  
I’ve added a rule to build `real_plugin.cmxs` with an external call to `ocamlfind`:

```auto
(rule
  (targets real_plugin.cmxs)
  (deps plugin.cmxa)
  (action (run ocamlfind ocamlopt
                  -shared
                  -linkall
                  -linkpkg
                  -dontlink lwt.unix
                  -thread
                  -package lablgtk2,lwt_glib
                  %{deps}
                  -o %{targets}
  )))

```

That works (change `myprog.ml` to load `real_plugin.cmxs` instead of `plugin.cmxs`):

```auto
$ make
dune build @all
dune exec ./myprog.exe
Plugin init..."LC_CTYPE=en_GB.UTF-8;LC_NUMERIC=C;LC_TIME=en_GB.UTF-8;LC_COLLATE=en_GB.UTF-8;LC_MONETARY=en_GB.UTF-8;LC_MESSAGES=en_GB.UTF-8;LC_PAPER=en_GB.UTF-8;LC_NAME=en_GB.UTF-8;LC_ADDRESS=en_GB.UTF-8;LC_TELEPHONE=en_GB.UTF-8;LC_MEASUREMENT=en_GB.UTF-8;LC_IDENTIFICATION=en_GB.UTF-8"
Plugin loaded OK

```

However, it’s a bit ugly. Also, adding `real_plugin.cmxs` to an install stanza forces Dune to try to build the plugin even if lablgtk isn’t available, ignoring the `(optional)` in the library definition.

I also tried using `ocamlfind` to get the archives:

```auto
(library
 (name plugin)
 (optional)
 (ocamlopt_flags :standard (:include gtk_archives.sexp))
 (libraries lablgtk2 lwt_glib))

(rule
  (targets gtk_archives.sexp)
  (action
    (with-stdout-to %{targets}
      (run ocamlfind query -r lablgtk2,lwt_glib -format "\"%+a\"" -predicates native -prefix "(" -suffix ")"
    ))
  ))

```

However, that fails with:

```auto
$ make
dune build @all
    ocamlopt plugin/plugin.{a,cmxa} (exit 2)
(cd _build/default && /home/user/.opam/4.07.0/bin/ocamlopt.opt -w @a-4-29-40-41-42-44-45-48-58-59-60-40 -strict-sequence -strict-formats -short-paths -keep-locs -g /home/user/.opam/4.07.0/lib/lablgtk2/lablgtk.cmxa /home/user/.opam/4.07.0/lib/result/result.cmxa /home/user/.opam/4.07.0/lib/lwt/lwt.cmxa /home/user/.opam/4.07.0/lib/ocaml/unix.cmxa /home/user/.opam/4.07.0/lib/ocaml/bigarray.cmxa /home/user/.opam/4.07.0/lib/lwt/unix/lwt_unix.cmxa /home/user/.opam/4.07.0/lib/lwt_glib/lwt_glib.cmxa -a -o plugin/plugin.cmxa plugin/.plugin.objs/plugin.cmx)
Option -a cannot be used with .cmxa input files.

```

What’s the correct way to build a plugin with Dune?

---

<div class="post-metadata">

**Author:** ![jeremiedimino](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/jeremiedimino/32/699_2.png) [@jeremiedimino](https://discuss.ocaml.org/u/jeremiedimino)\
**Post date:** [November 12, 2018, 2:42pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/2 "2018-11-12T14:42:44Z")

</div>

I believe that usually people use a plugin loader that supports dependencies between plugings such as `findlib.dynload`. It is then the responsibility of this loader to load the dependencies of `plugins.cmxs`. In particular, this allows several plugings to use the same dependency.

IIUC, you are trying to link all the dependency of your plugin statically in one cmxs. This is generally not recommended as the same library might end up being linked twice at runtime: either because it is present in both the main executable and the plugin, or because it is present in several plugins.

I would suggest to switch to something like `findlib.dynload`. Thanks to @bobot dune supports `findlib.dynload` out of the box, i.e. it makes sure that libraries that are statically linked in the main executable won’t be dynlinked at runtime.

---

<div class="post-metadata">

**Author:** ![talex5](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/talex5/32/353_2.png) [@talex5](https://discuss.ocaml.org/u/talex5)\
**Post date:** [November 12, 2018, 3:29pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/3 "2018-11-12T15:29:13Z")

</div>

Thanks. The target system typically won’t have findlib, so I doubt that will work (this is for 0install). It would certainly be nice if dune could link all the libraries needed by the plugin that aren’t already linked by the main executable (then I wouldn’t need `-linkall` and could shrink the binaries a bit). But before that, I’d be happy just to get back to where I was with ocamlbuild…

(note that this plugin is shipped alongside the main binary; I don’t need to support third-party plugins in this case)

---

<div class="post-metadata">

**Author:** ![jeremiedimino](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/jeremiedimino/32/699_2.png) [@jeremiedimino](https://discuss.ocaml.org/u/jeremiedimino)\
**Post date:** [November 12, 2018, 3:51pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/4 "2018-11-12T15:51:42Z")

</div>

Ok. Something would need to be added to dune to support this properly. If you are happy to spend a bit of time on making dune support this feature, I’m happy to point you in the right directory. Otherwise, I suggest to continue using the workaround you posted, in the end it’s not that bad. The only drawback is that it doesn’t work well if the dependencies are vendored, although this part should be easy to fix.

---

<div class="post-metadata">

**Author:** ![jeremiedimino](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/jeremiedimino/32/699_2.png) [@jeremiedimino](https://discuss.ocaml.org/u/jeremiedimino)\
**Post date:** [November 15, 2018, 2:23pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/5 "2018-11-15T14:23:33Z")

</div>

For future reference, a similar ticket was just opened: [https://github.com/ocaml/dune/issues/1544](https://github.com/ocaml/dune/issues/1544)

---

<div class="post-metadata">

**Author:** ![talex5](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/talex5/32/353_2.png) [@talex5](https://discuss.ocaml.org/u/talex5)\
**Post date:** [November 15, 2018, 3:24pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/6 "2018-11-15T15:24:27Z")

</div>

I’ve got it working now. Here’s the final `dune` file I ended up with for the plugin:

```auto
(* -*- tuareg -*- *)

let have_gtk =
  try
    ignore @@ Jbuild_plugin.V1.run_and_read_lines "ocamlfind query lablgtk2";
    true
  with _ ->
    print_endline "(will skip building the GTK plugin)";
    false

let plugin_ext =
  match List.assoc "natdynlink_supported" Jbuild_plugin.V1.ocamlc_config with
  | "true" -> "cmxs"
  | _ -> "cma"
  | exception Not_found -> "cma"

let dune = {|
  (library
   (name gui_gtk_lib)
   (libraries lablgtk2 lwt_glib zeroinstall))

  (rule
    (targets gui_gtk.cmxs)
    (deps gui_gtk_lib.cmxa)
    (action (run ocamlfind ocamlopt -shared -linkall -linkpkg
			  -dontlink lwt,lwt.unix
			  -thread
			  -package lablgtk2,lwt_glib
			  %{deps}
			  -o %{targets}
    )))

  (rule
    (targets gui_gtk.cma)
    (deps gui_gtk_lib.cma)
    (action (run ocamlfind ocamlc -linkall -linkpkg
			  -dontlink lwt,lwt.unix
			  -thread -a
			  -package lablgtk2,lwt_glib
			  %{deps}
			  -o %{targets}
    )))

  (install
    (section lib)
|} ^ Printf.sprintf "(files gui_gtk.%s))" plugin_ext

let () =
  Jbuild_plugin.V1.send (if have_gtk then dune else "")

```

(from [https://github.com/0install/0install/blob/master/ocaml/gui\_gtk/dune](https://github.com/0install/0install/blob/master/ocaml/gui_gtk/dune))

Some things I’m not quite happy with:

- I use `natdynlink_supported` to decide whether to build a native or bytecode plugin. I guess I really want to use whatever the main executable is using.

- I had to use the tuareg hack to skip the `(install)` section if the plugin isn’t being built. Would be nice if you could do `(install (only-if %{lib-available:lablgtk2}) ...)` or something like that.

- There’s some duplication for handing both `cma` and `cmxs`, although the invocations aren’t quite the same.

Apart from that it worked quite well. For the unit-tests, I just added a dependency on `(alias ../../install)`, so I didn’t need any conditionals there in the end.

The other (unrelated) source of ugliness was special cases for building on Windows (see [https://github.com/0install/0install/blob/master/ocaml/support/dune](https://github.com/0install/0install/blob/master/ocaml/support/dune)).

- I seemed to need another `tuareg` hack to locate the correct version of `windres` (needed when linking binaries on Windows, due to this craziness: [http://social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/73f86e9e-928f-40b7-8dd5-27e40db6997e/](http://social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/73f86e9e-928f-40b7-8dd5-27e40db6997e/)).

- Some C stubs are only needed on Windows (for querying the registry) and so need to be build conditionally.

If anyone spots a way to make these dune files cleaner, let me know!

---

<div class="post-metadata">

**Author:** ![bobot](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/bobot/32/198_2.png) [@bobot](https://discuss.ocaml.org/u/bobot)\
**Post date:** [November 16, 2018, 9:13am UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/7 "2018-11-16T09:13:32Z")

</div>

Why ocamlfind can’t be installed in 0install? How 0install handles ocaml libraries and shared C libraries?

---

<div class="post-metadata">

**Author:** ![talex5](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/talex5/32/353_2.png) [@talex5](https://discuss.ocaml.org/u/talex5)\
**Post date:** [March 1, 2020, 6:33pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/8 "2020-03-01T18:33:59Z")

</div>

@nojb has opened a PR on dune that supports this very nicely now:

> <https://github.com/ocaml/dune/pull/3141#issuecomment-586622133>

---

<div class="post-metadata">

**Author:** ![gasche](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/gasche/32/4_2.png) [@gasche](https://discuss.ocaml.org/u/gasche)\
**Post date:** [March 27, 2021, 4:52pm UTC](https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874/9 "2021-03-27T16:52:16Z")

</div>

I found this issue while hitting a related but different, simpler `-linkpkg`-looking problem with Dune: getting a C library (that is part of the current project) linked into an executable that uses it (from C stubs, obviously). I ended up opening an upstream issue: [dune #4409: confused by Dune foreign\_libraries doc: how to define a C library used by C stubs?](https://github.com/ocaml/dune/issues/4409)
