Let me begin by saying that I'm a complete beginner in OCaml, so if I seem to have made some weird choices, I'm most likely not aware of them being a choice.
I am trying to get unit testing working in my project. After some searching I settled on qtest.lib.
I've set up my project as follows:
$ mkdir mylib
$ cd mylib
$ dune init lib mylib
In the dune file, I've written:
(library
(name mylib)
(inline_tests (backend qtest.lib)))
In mylib.ml, I've put the following code:
let foo x = x + 2
(*$T foo
foo 2 = 4
*)
At this point, everything works as expected:
$ dune runtest
Info: Creating file dune-project with this contents:
| (lang dune 2.9)
inline_test_runner_mylib alias runtest
random seed: 425752161
[1 / 1] >foo>mylib.ml:4 *
[1 / 1] >foo>;32;1mSUCCESS
The issues start if I try to introduce another file into the project. I created helper.ml with the following contents:
let bar x = 3 * x
(*$T bar
bar 3 = 9
*)
Now, dune runtest errors out with
$ dune runtest
File "mylib.ml", line 11, characters 5-11:
Error: Unbound module Helper
In some of my other attempts at reproducing it, the file mentioned was instead _build/default/.mylib.inline-tests/inline_test_runner_mylib.ml-gen.
I first assumed that this means I'm organizing my files incorrectly. However, I can access Helper.bar within mylib.ml:
$ cat mylib.ml
let foo x = Helper.bar (x + 2)
$ dune build # no errors
Thus I have no idea what the problem could be here. What's going on?
Strangely it looks like you need to put
(modules)
in your dune file (as you can see here)
Your dune file will look like:
(library
(name mylib)
(modules)
(inline_tests (backend qtest.lib)))
Dune wraps library by default in a module with the same name as the library. For instance, defining a mylib library with
a.ml
b.ml
will create a Mylib module with A and B as submodule.
However, if you define a mylib module by hand, dune consider that this module is the entry point of your library, and it is your responsibility to expose all visible submodule.
Thus if you define mylib.ml as:
let x = 0
you are explicitly hiding the Helper module.
The simpler option here is probably to rename the Mylib module.
Another issue is that the qtest backend does not seem aware of dune wrapping libraries. A potential workaround is to define your own inline test backend and add an open Mylib to the preamble
(library (name myqtest)
(modules)
(inline_tests.backend
(runner_libraries qcheck ounit2 bytes)
(generate_runner (run qtest extract --preamble "open Myib" --quiet %{impl-files} %{intf-files}))
)
)
(library (name mylib)
(inline_tests
(backend myqtest)
)
)
(I hope that I am missing an option and that there is a simpler solution to send a flag to the runner generator)
Related
I'm building a project using dune and I'm facing the following situation. Essentially, my project depends on another project, not developed by me, from which I want to use some parts of the source code.
Here is more or less my project tree
my_project/
|---dune-project
|---src/
|---dune
|---extrenal-project/
|---dune-project
|---dune
|---src/
|---dune
|---src-file.ml
The problem is that external-project has a dune file that builds it as an executable, instead of a library. Therefore, I cannot include it as a library in my dune file. Ideally, I don't want to modify the dune file inside of external-project.
I've tried a series of combinations. Here is the current status of my dune file:
(dirs external-project)
(executable
(name myexec)
(libraries containers)
(modules Myexec)
(promote (until-clean) (into ".."))
)
(env (dev (flags (:standard -warn-error -A))))
Which allows me to compile external-project correctly, but then does not allow me to reference any of its files. I also tried (dirs external-project external-project/src) but to no success.
So, my question is: is there a way for me to reference external-project as a library, even though it is build as an executable?
Thanks for your help!
What is the namespace of the test0 definition in dune utop . environment, given the following ocaml code with dune build definition:
~/_ocaml_/n01$ cat bin/dune
(executable
(public_name n01)
(name main))
~/_ocaml_/n01$ cat bin/main.ml
let test0 = "World!"
let () = print_endline ("Hello " ^ test0)
When started the code produces expected result:
~/_ocaml_/n01$ dune build
~/_ocaml_/n01$ dune exec n01
Hello World!
I believe that your issue is that you cannot access the test0 variable after launching dune utop ..
If I am not mistaken, your issue stems from the fact that the dune top <path> command loads libraries and not executable. Thus in your setup, the N01 module is not part of the utop environment. The idiomatic solution is to split your source files into a library and a small executable that only contains glue code.
I am using Dune for building OCaml projects and I'd like to build a standalone library for reusing it in other projects.
The root folder of the library is mylib and the library was initialized with dune init lib mylib src (after cd mylib).
The directory tree of mylib, the root of the project, is the following:
mylib
+---dune-project
│
+---src
dune
priv.ml
mymodule.ml
mymodule.mli
Here follows the content of the files.
mylib/dune-project:
(lang dune 2.7)
mylib/src/dune:
(library
(name mylib))
mylib/src/priv.ml:
let rec loop a accu i =
let n = Array.length a in
if i = n then
accu
else
loop a (accu + Array.unsafe_get a i) (succ i)
mylib/src/mymodule.mli:
val sum : int array -> int
mylib/src/mymodule.ml:
let sum a =
Priv.loop a 0 0
After giving context and showing the content of each file of this toy example, the question follows:
How can I build the library mylib and use it in another, separate project?
(For example, in another project, I'd consume the library with the following:
Open Mylib
let () =
print_int (Mymodule.sum [1;2;3])
OR
let () =
print_int (Mylib.Mymodule.sum [1;2;3])
)
With Dune, given the executable main, you would write the following dune file to use library mylib if it was published on Opam.
dune:
(executable
(name main)
(libraries mylib))
After successfully building the library and linking it in another project, how can I hide or not expose some modules? For example, given the toy library of before, mylib, I would like to not expose and make inaccessible the module Priv (such that Priv would only be usable inside modules of mylib - like a protected/internal class in Java/C#).
I tried searching on Real World OCaml, Cornell CS3110 textbook, Dune documentation, OCaml Learn but, unless deeply nested, I found nothing.
Thank you very much for your help, if I didn't explain something in a clear way, please ask and I will try to explain better.
In the root directory, these should be a file called mylib.opam with content similar to this one. Then, the (public_name mylib) s-expression should be added to library in mylib/src/dune. Finally, making sure to be in the root of the project, the library shall be built with dune build and installed with opam install ..
For actually using the library in another project, even in a different dune workspace, all it's needed is to add (libraries mylib) to the dune file of project that will use the library.
The (private_modules priv) s-expression should be added to library in mylib/src/dune. Then, mylib.ml needs to be created with the following content: module Mymodule = Mymodule: this will make sure that only Mymodule will be exposed under the package Mylib.
I'm using NixOS and compiling the cohttp server example using dune. The example is notable in that it links to two C-libraries: openssl and libev.
initial attempt
Here's my shell.nix:
with import <nixpkgs> { };
let spec = {
buildInputs = with ocamlPackages; [
ocaml
findlib
dune
# ocaml libs (and external library deps)
cohttp-lwt-unix openssl libev
]);
};
in runCommand "dummy" spec ""
Here's my dune file:
(executable
(name server_example)
(libraries cohttp-lwt-unix))
And the output of dune build server_example.exe
...
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lssl
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lcrypto
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lev
Ok, not terribly surprising since these are in non-standard locations in NixOS. I need to add the relevant paths to the ocamlopt commandline invoked by dune, e.g.: -I ${openssl.out}/lib -I ${libev}/lib.
Now, openssl includes pkg-config files, but adding pkg-config to my shell.nix has no apparent effect.
second attempt, using configurator
I used configurator to create a program to add flags from an environment variable to the build flags of my dune executable.
shell.nix
with import <nixpkgs> { };
let spec = {
buildInputs = with ocamlPackages; [
ocaml
findlib
dune
configurator
# ocaml libs (and external library deps)
cohttp-lwt-unix openssl libev
]);
shellHook = ''
export OCAML_FLAGS="-I ${openssl.out}/lib -I ${libev}/lib"
'';
};
in runCommand "dummy" spec ""
dune
(executable
(name server_example)
(flags (:standard (:include flags.sexp)))
(libraries cohttp-lwt-unix))
(rule
(targets flags.sexp)
(deps (:discover config/discover.exe))
(action (run %{discover})))
config/dune
(executable
(name discover)
(libraries dune.configurator))
config/discover.ml
open Sys
module C = Configurator.V1
let () =
C.main ~name:"getflags" (fun _c ->
let libs =
match getenv_opt "OCAML_FLAGS" with
| None -> []
| Some flags -> C.Flags.extract_blank_separated_words flags
in
C.Flags.write_sexp "flags.sexp" libs)
Compilation now succeeds, but this approach of writing a custom program to take an environment variable and put it into the flags argument seems clunky.
Is there a standard way to accomplish this in dune (adding paths to ocamlopt commandline with -I)?
If not, is there an easier way to read an environment variable from within dune files?
Use stdenv.mkDerivation or (as suggested by Robert Hensing above) ocamlPackages.buildDunePackage instead of runCommand. The latter results in an environment that does not include openssl and libev in the NIX_CFLAGS_COMPILE or NIX_LDFLAGS environment variables. Using mkDerivation does result in those variables being populated.
Once those variables are in place, compilation via dune succeeds, and the executable is linked to libssl and libev.
Furthermore, explicit inclusion of libev and openssl is unnecessary since they are declared as propagated build inputs of through cohttp-lwt-unix.
shell.nix:
with import <nixpkgs> { };
with ocamlPackages;
buildDunePackage {
pname = "dummy";
version = "0";
buildInputs = [
cohttp-lwt-unix
];
}
I am trying to use ocamlfind to install a library. I am using OCamlMakeFile. Here is my Makefile:
OCAMLMAKEFILE = OCamlMakeFile
RESULT = owebl
SOURCES = src/utils.ml src/verb.ml src/request.ml src/template.ml src/response.ml src/rule.ml src/handler.ml src/server.ml
PACKS = unix str
all: native-code-library byte-code-library
install: libinstall
uninstall: libuninstall
include $(OCAMLMAKEFILE)
So basically the library is a collection of modules that I want to distribute. A basic usage of the library is (main.ml):
open Response
open Rule
open Verb
open Server
let handler =
Handler.create
(StaticRouteRule.create "/" [GET])
(FileResponse.create
~static_file:(FileResponse.StaticFile "/index.html")
())
let server = Server.create "0.0.0.0" 8080 [handler];;
server#serve
I compile the library just by running "make". It generates three files: "owebl.a, owebl.cma, owebl.cmxa". Then I install the library using ocamlfind:
ocamlfind install owebl META owebl.a owebl.cma owebl.cmxa
Oh, the META file is:
version = "0.1"
name = "OWebl"
description = "A Web Framework for OCaml"
archive(byte) = "owebl.cma"
archive(native) = "owebl.cmxa"
requires = "unix,str"
Everything works until I try to compile the above example that uses the library. I run:
ocamlbuild -use-ocamlfind -pkgs owebl main.native
And I get:
ocamlbuild -use-ocamlfind -pkgs owebl main.native
+ ocamlfind ocamlc -c -package owebl -o main.cmo main.ml
File "main.ml", line 1, characters 5-10:
Error: Unbound module Owebl
Command exited with code 2.
Hint: Recursive traversal of subdirectories was not enabled for this build,
as the working directory does not look like an ocamlbuild project (no
'_tags' or 'myocamlbuild.ml' file). If you have modules in subdirectories,
you should add the option "-r" or create an empty '_tags' file.
To enable recursive traversal for some subdirectories only, you can use the
following '_tags' file:
true: -traverse
<dir1> or <dir2>: traverse
Compilation unsuccessful after building 2 targets (1 cached) in 00:00:00.
make: *** [fileserver] Error 10
I tried adding open Owebl to the beginning of main.ml but I just got "unbound module Owebl" similarly. I'm pretty lost as to why these modules are all unbound. What am I missing?
You should use LIB_PACK_NAME variable if you want to pack your modules under umbrella namespace, like OWebl, otherwise they will be available just as it is, e.g. Response, Rule, etc. Also, you should specify the mli files as well as ml files in SOURCES variable. And finally, you shouldn't forget to install cmi files, as well as .a files. Installing mli files is also considered a good tradition, although is not strictly required. Also, consider using OCamlMakefile installation facilities instead of custom installation script.
I am no longer using OCamlMakefile therefore am not sure, but assuming OCamlMakefile compiles things with -for-pack OWebl option and properly builds a packaged module owebl.cmo, you also need to install owebl.cmi.
OCaml compiler seeks cmi files to check the existence of modules and to get their type information. If not installed, the compiler cannot find it. cma and cmxa files are collections of objects and do not provide type information.