[SOLVED] From oasis to jbuilder : module not found when running the tests

I want to use jbuilder instead of oasis in my little lib. Here is the _oasis file :

Name: libmpdclient
Version: 0.1
Synopsis: A mpd client library.
Authors: cedlemo <cedlemo@gmx.com>
License: GPL-3

Description: This is an attempt to write a client library for the mpd server protocol.
Homepage: https://github.com/cedlemo/OCaml-libmpdclient

OASISFormat: 0.4
BuildTools: ocamlbuild
Plugins: META (0.4), DevFiles (0.4)

Library "libmpdclient"
  Path: src
  Modules: Mpd,
           Protocol,
           Playback, LwtPlayback,
           PlaybackOptions, LwtPlaybackOptions,
           Status,
           Song,
           MpdQueue, MpdLwtQueue,
           StoredPlaylists, LwtStoredPlaylists,
           Mpd_utils
  BuildDepends: unix, str, lwt, lwt.unix

Executable mpdclient_test_unit
  Path: tests
  Build$: flag(tests)
  Install: false
  CompiledObject: best
  MainIs: test.ml
  BuildDepends: oUnit, libmpdclient

Test "mpdclient_test_unit"
  Run$: flag(tests)
  TestTools: mpdclient_test_unit
  Command: $mpdclient_test_unit
  WorkingDirectory: tests

As you see, all the modules of my lib are in the src/ directory and the test file is in the tests directory. I build and test with the following commands :

#!/usr/bin/bash
oasis setup -setup-update dynamic
./configure --enable-tests
make test

With oasis, everything was working.

In order to use jbuilder, I created the ocaml-libmpdclient.opam file with the following content :

opam-version: "1.2"
name: "OCaml-libmpdclient"
version: "~unknown"
maintainer: "cedlemo <cedlemo@gmx.com>"
authors: "cedlemo <cedlemo@gmx.com>"
homepage: "https://github.com/cedlemo/OCaml-libmpdclient"
bug-reports: "https://github.com/cedlemo/OCaml-libmpdclient/issues"
license: "GPL3"
dev-repo: "https://github.com/cedlemo/OCaml-libmpdclient"
build: [
  ["./configure" "--prefix=%{prefix}%"]
  [make]
]
install: [make "install"]
remove: ["ocamlfind" "remove" "OCaml-libmpdclient"]
depends: [
  "ocamlfind" {build}
]

then I created a src/jbuild file :


(jbuild_version 1)

(library
 ((name        Mpd)
  (public_name ocaml-libmpdclient)
  (libraries (unix lwt str))
 )
)

and a tests/jbuild file :

(executables
 ((names (test))
  (libraries (ocaml-libmpdclient lwt oUnit))
))

(alias
 ((name    runtest)
  (deps    (test.exe))
  (action  (run ${<}))
 )
)

When I run jbuilder build, I have the output below and everything seems to work :

ocamlc src/Mpd__.{cmi,cmo,cmt}
    ocamldep src/Mpd.dependsi.ocamldep-output
    ocamldep src/Mpd.depends.ocamldep-output
      ocamlc src/Mpd__Song.{cmi,cmti}
      ocamlc src/Mpd__Protocol.{cmi,cmti}
      ocamlc src/Mpd__Status.{cmi,cmti}
      ocamlc src/Mpd__Mpd_utils.{cmi,cmo,cmt}
      ocamlc src/Mpd__Protocol.{cmo,cmt}
      ocamlc src/Mpd__Song.{cmo,cmt}
      ocamlc src/Mpd__Status.{cmo,cmt}
    ocamlopt src/Mpd__.{cmx,o}
      ocamlc src/mpd.{cmi,cmo,cmt}
    ocamlopt src/Mpd__Protocol.{cmx,o}
      ocamlc src/Mpd__LwtPlaybackOptions.{cmi,cmti}
      ocamlc src/Mpd__LwtPlayback.{cmi,cmti}
    ocamlopt src/Mpd__Mpd_utils.{cmx,o}
      ocamlc src/Mpd__LwtStoredPlaylists.{cmi,cmti}
      ocamlc src/Mpd__MpdLwtQueue.{cmi,cmti}
      ocamlc src/Mpd__Playback.{cmi,cmti}
      ocamlc src/Mpd__MpdQueue.{cmi,cmti}
      ocamlc src/Mpd__PlaybackOptions.{cmi,cmti}
      ocamlc src/Mpd__StoredPlaylists.{cmi,cmti}
      ocamlc src/Mpd__LwtPlaybackOptions.{cmo,cmt}
      ocamlc src/Mpd__LwtPlayback.{cmo,cmt}
      ocamlc src/Mpd__LwtStoredPlaylists.{cmo,cmt}
      ocamlc src/Mpd__MpdLwtQueue.{cmo,cmt}
      ocamlc src/Mpd__Playback.{cmo,cmt}
    ocamlopt src/Mpd__Song.{cmx,o}
      ocamlc src/Mpd__MpdQueue.{cmo,cmt}
      ocamlc src/Mpd__PlaybackOptions.{cmo,cmt}
      ocamlc src/Mpd__StoredPlaylists.{cmo,cmt}
      ocamlc src/Mpd.cma
    ocamlopt src/Mpd__Status.{cmx,o}
    ocamlopt src/mpd.{cmx,o}
    ocamlopt src/Mpd__LwtPlayback.{cmx,o}
    ocamlopt src/Mpd__LwtPlaybackOptions.{cmx,o}
    ocamlopt src/Mpd__LwtStoredPlaylists.{cmx,o}
    ocamlopt src/Mpd__MpdLwtQueue.{cmx,o}
    ocamlopt src/Mpd__Playback.{cmx,o}
    ocamlopt src/Mpd__PlaybackOptions.{cmx,o}
    ocamlopt src/Mpd__StoredPlaylists.{cmx,o}
    ocamlopt src/Mpd__MpdQueue.{cmx,o}
    ocamlopt src/Mpd.{a,cmxa}
    ocamlopt src/Mpd.cmxs

but when I run the tests with jbuilder runtest, I have an error about an unbound module :

jbuilder runtest
    ocamldep tests/test.depends.ocamldep-output
      ocamlc tests/test.{cmi,cmo,cmt} (exit 2)
(cd _build/default && /usr/bin/ocamlc.opt -w -40 -g -bin-annot -I /home/cedlemo/.opam/system/lib/bytes -I /home/cedlemo/.opam/system/lib/lwt -I /home/cedlemo/.opam/system/lib/oUnit -I /home/cedlemo/.opam/system/lib/result -I /usr/lib/ocaml -I src -no-alias-deps -I tests -o tests/test.cmo -c -impl tests/test.ml)
File "tests/test.ml", line 24, characters 5-13:
Error: Unbound module Protocol

What am I missing ?

2 Likes

You should add (wrapped false) as an option in your library stanza. Otherwise, all of your modules will be available under the Mpd prefix, e.g. Mpd.Protocol.

3 Likes

Thanks , it works.

The new src/jbuilder



(jbuild_version 1)

(library
 ((name        Mpd)
  (public_name ocaml-libmpdclient)
  (libraries (unix lwt str))
  (wrapped false)
 )
)

I find this topic very interesting because it allows to compare the way to do with Jbuilder and Oasis.
Not yet completely convinced by Jbuilder, I take the opportunity to ask a question that titillates me.
What is the motivation to migrate from Oasis to Jbuilder?

With regard to the proposed solution, is it better to use (Wrapped false) or Mpd.Protocol?

Thank you for your answers.

1 Like

Since I am a beginner, I can not answer you.

I used Mpd and Protocol because I wanted to set the modules in different files for readability and oasis didn’t wrap Protocol in Mpd. But this is a really good question and you should create a new subject, in order to have a better visibility for it.

Your answer already sheds light on the matter.
I realize that Jbuilder automatically wrap the modules of a library in a module of the name of this library. It would be possible to do this independently of the build system using the module aliases but this has to be done manually.
Thank’s again.

Yes, and if you don’t do this, you expose all modules which creates issues down the line when somebody tries to use e.g. Jane Street Base with Msgpack, because both have a module called Base and the compiler does not know which one to pick up.

1 Like

In my experience, JBuilder is a much faster build system. Oasis generates a Makefile for your project which then uses Ocamlbuild and ocamlfind to do the actual building. From what I can tell, JBuilder figures out the dependencies and then directly invokes the OCaml compiler. I think this directness results in much faster builds.

I prefer the latter for the reasons @Leonidas mentioned above.

1 Like

@Leonidas, @gndl,

thanks for the information, it is really helpful. I find the related part of the documentation
http://jbuilder.readthedocs.io/en/latest/jbuild.html?highlight=wrapped#library

(wrapped ) specifies whether the modules of the library should be available only through the top-level library module, or should all be exposed at the top level. The default is true and it is highly recommended to keep it this way. Because OCaml top-level modules must all be unique when linking an executables, polluting the top-level namespace will make your library unusable with other libraries if there is a module name clash. This option is only intended for libraries that manually prefix all their modules by the library name and to ease porting of existing projects to Jbuilder

I have another question :

How can you make it work when you need/want types, values or functions directly inside the namespace of your lib Mpd like Mpd.a_value Mpd.a_function. Because with jbuilder, if I create a mpd.ml file, in the lib/ directory, I need to use (wrapped false) in the jbuild file in order to be able to build my lib. Should I organize the code differently like create a functions.ml file and get the function I need with Mpd.Functions.a_function ?

It is also my feeling :slight_smile: