I’m working on libdash, which is offers OCaml and Python bindings to the dash POSIX shell parser. dune is being uncooperative around packaging the library up properly (i.e., with both bindings and code). My work is in the ocaml-static-ctypes
branchmain
branch (since OCaml bindings don’t work right now anyway).
The structure is:
- an autotools build constructs
libdash.a
anddlldash.so
from the (lightly modified dash source) - some static ctypes bindings connects the C interface to OCaml
- a bunch of OCaml converts the C ASTs into more sensible structures
- some executables use the OCaml ASTs to do some work
A critical property here is that the C library is not a common one, and must be distributed with the OCaml source. The produced OCaml native library should include all of the C code.
dune woes
I—after several years of trying off and on—have gotten dune to build the project locally and have tests pass. (I posted here three years ago.) But there’s a problem: the build fails with multiple uses of libdash.a
. Here’s the output:
$ dune build
Error: Multiple rules generated for
_build/install/default/lib/libdash/libdash.a:
- ocaml/dune:8
- ocaml/dune:8
-> required by _build/default/libdash.install
-> required by alias all
-> required by alias default
The offending clause:
(library
(name libdash)
(public_name libdash)
(modes native)
(modules (:standard \ json_to_shell shell_to_json ast_json))
(libraries ctypes ctypes.foreign)
(foreign_archives ../dash)
(ctypes
(external_library_name dash)
(build_flags_resolver (vendored (c_flags :standard) (c_library_flags :standard)))
(deps (glob_files ../src/*.h) ../src/builtins.h ../src/nodes.h ../src/syntax.h ../src/token.h ../src/token_vars.h)
(headers (preamble
"\
\n#include \"../src/shell.h\"\
\n#include \"../src/memalloc.h\"\
\n#include \"../src/mystring.h\"\
\n#include \"../src/init.h\"\
\n#include \"../src/main.h\"\
\n#include \"../src/input.h\"\
\n#include \"../src/var.h\"\
\n#include \"../src/alias.h\"\
\n#include \"../src/redir.h\"\
\n#include \"../src/parser.h\"\
\n#include \"../src/nodes.h\"\
\n"))
(type_description
(instance Types)
(functor Type_description))
(function_description
(instance Functions)
(functor Function_description))
(generated_types Types_generated)
(generated_entry_point Cdash)))
In particular, the problem is my use of (foreign_archives ../dash)
. This, however, is the key line that makes the executables work. Without it, the linker fails when building the executables and it can’t find libdash’s symbols.
linking woes
The build works fine if I move (foreign_archives ../dash)
to the executables, but then there’s a different problem: others trying to link with libdash
won’t get the actual libdash.a
and those clients will fail at link time with missing symbols. I’ve tried using an install
stanza to make sure libdash.a
is in (section lib)
, but it has no effect on libraries that then try to link with dash.
what should i do?
The library is currently built with custom ocamlfind
commands that once worked but no longer work on Linux. They’re fine on macOS; it’s a similar linking issue.
My feeling is that this is a bad interaction between ctypes and dune, and I should simply be able to do this. (I don’t see the issue of the multiple dependency on libdash.a
, which should indeed be used more than once. That’s what libraries are for!) But I would not be the first person to have wrongly felt “I should simply be able to do this”! So… what do I do?
I see a few ways forward, in order of preference:.
-
Figure out the magic incantation that lets dune actually package the C code with the library.
-
Give up on Ctypes entirely and interface with C more directly, allowing me to use
foreign_archives
with the library. (This is the approach taken by re2.) -
Give up on OCaml bindings (I am the only client; everyone else uses Python—and I could too, at the cost of some communication overhead).
-
Create a
conf-libdash
that does the C library installation and have the OCaml bindings depend on that.
I put (4) last because it is a remarkable amount of faffing about for something that should be really very simple… making me more inclined to take a performance hit in order to no longer need to interact with OCaml (which has been incredibly frustrating in its interaction with C… my last post on this was in 2021 2020).