├── .gitignore
├── README.md
├── UNLICENSE
└── proj
├── .gitignore
├── .ocamlformat
├── Makefile
├── README.md
├── dune-project
├── proj.opam
├── sub1
├── lib
│ ├── a.ml
│ ├── a.mli
│ ├── b.ml
│ └── dune
└── test
│ ├── a.ml
│ ├── b.ml
│ └── dune
├── sub2
├── bin
│ ├── bar_main.ml
│ ├── dune
│ └── foo_main.ml
├── lib
│ ├── a.ml
│ └── dune
└── test
│ ├── a.ml
│ └── dune
├── test
└── tests
├── dune
└── run_tests.ml
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.annot
3 | *.cmo
4 | *.cma
5 | *.cmi
6 | *.a
7 | *.o
8 | *.cmx
9 | *.cmxs
10 | *.cmxa
11 | _build
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dune starter kit
2 |
3 | This repository provides templates to help you start an
4 | OCaml project. It can be used to create multiple libraries, multiple
5 | executables, and test suites.
6 |
7 | The project is structured as a collection of mostly self-contained
8 | subprojects, each with its source code and tests.
9 |
10 | Requirements: opam, git, make, dune.
11 |
12 | ## How to set up your OCaml project:
13 |
14 | 1. Clone this repository:
15 | `git clone https://github.com/mjambon/dune-starter`
16 | 2. Copy files into a git repository e.g.
17 | `cp -a dune-starter/proj foobar`,
18 | `cd foobar && git init && git add .`
19 | 3. Play around and make sure everything works. Try `make setup`
20 | to install the missing Opam packages, `make` to build the project,
21 | `make test`, `make install`, `make uninstall`, `make clean`.
22 | Consult the project's readme (`proj/README.md`) for more info.
23 | 4. Replace occurrences of `proj`, `sub1` and `sub2` by your own names.
24 | Rename, throw away, and add files as needed.
25 | 5. Consult the [dune docs](https://dune.readthedocs.io/) as
26 | needed.
27 |
28 | Thanks to the authors of dune and @rgrinberg in particular for
29 | this great tool!
30 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/proj/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | _build
3 | .merlin
4 | *.install
5 |
--------------------------------------------------------------------------------
/proj/.ocamlformat:
--------------------------------------------------------------------------------
1 | version=0.17.0
2 |
--------------------------------------------------------------------------------
/proj/Makefile:
--------------------------------------------------------------------------------
1 | # Frontend to dune.
2 |
3 | .PHONY: default
4 | default: build
5 |
6 | # Install the opam packages other than 'dune'
7 | .PHONY: setup
8 | setup:
9 | dune build proj.opam
10 | opam install --deps-only ./proj.opam
11 |
12 | .PHONY: build
13 | build:
14 | dune build
15 |
16 | # To run selected tests, or to review and approve tests, run './test' manually.
17 | # See Testo's tutorial to get started.
18 | .PHONY: test
19 | test:
20 | dune build tests/run_tests.exe
21 | ./test
22 |
23 | .PHONY: install
24 | install:
25 | dune install
26 |
27 | .PHONY: uninstall
28 | uninstall:
29 | dune uninstall
30 |
31 | .PHONY: clean
32 | clean:
33 | dune clean
34 | # Optionally, remove all files/folders ignored by git as defined
35 | # in .gitignore (-X).
36 | git clean -dfXq
37 |
38 | .PHONY: fmt
39 | .IGNORE: fmt
40 | fmt:
41 | dune build @fmt
42 | dune promote
43 |
--------------------------------------------------------------------------------
/proj/README.md:
--------------------------------------------------------------------------------
1 | _Catchy headline_
2 | ==
3 |
4 | _Project description_
5 |
6 | How to build the project
7 | --
8 |
9 | Run `make` to compile the libraries and executables that are
10 | meant to be installed.
11 | ```
12 | $ make
13 | ```
14 |
15 | How to run tests
16 | --
17 |
18 | ```
19 | $ make test
20 | ```
21 |
22 | How to use local libraries interactively
23 | --
24 |
25 | Use `dune utop DIR` where DIR if the folder contains the `dune`
26 | file for a library. For instance, our `sub2` sample library can be
27 | used as follows:
28 |
29 | ```
30 | $ dune utop sub2/lib
31 | ...
32 | utop # Proj_sub2.A.do_something ();;
33 | 1525373137.245 seconds have elapsed since 1970-01-01T00:00:00.
34 | - : unit = ()
35 | ```
36 |
37 | Installation
38 | --
39 |
40 | The project can be installed with or without opam.
41 | Without opam, you can run the following which relies directly on
42 | dune:
43 | ```
44 | $ make install
45 | ```
46 | Similarly:
47 | ```
48 | $ make uninstall
49 | ```
50 |
51 | With opam, you can install the current development version of your
52 | project as a single opam package. It will override the currently
53 | installed package of the same name, if any:
54 | ```
55 | $ opam pin add -k path proj .
56 | ```
57 | For more information on `opam pin`, please consult the
58 | [opam documentation](https://opam.ocaml.org/doc/Usage.html)
59 |
60 | The advantage of the opam-based method is that other opam packages can
61 | depend on this one, and opam will recompile them automatically as
62 | necessary.
63 |
--------------------------------------------------------------------------------
/proj/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 2.8)
2 | (name proj) ; optional project name
3 | (version 0.0.0) ; optional version for your project
4 |
5 | ; Generate *.opam files that can be used to install dependencies
6 | ; and later can be uploaded to opam-repository for distribution
7 | ; via Opam.
8 | (generate_opam_files)
9 |
10 | ; Choose a license if you're going to distribute your project
11 | ; A list of standard license identifiers is available at
12 | ; https://spdx.org/licenses/
13 | (license "ISC")
14 |
15 | ; Project source and homepage, pre-filled for a project hosted by GitHub:
16 | (source (github USERNAME/PROJ))
17 | (homepage "https://github.com/USERNAME/PROJ")
18 |
19 | ; Specify one or more maintainers and authors.
20 | ; A list of author names can be obtained mechanically from git with
21 | ; 'git shortlog -s -n'.
22 | (maintainers "NAME ")
23 | (authors "NAME")
24 |
25 | ; Opam packages defined by this project. One or more packages can be defined.
26 | ; Each package can provide any number of executables and libraries.
27 | ; The mapping from library or executable to a package is done in each
28 | ; 'dune' file.
29 | (package
30 | (name proj)
31 | (synopsis "CATCHY HEADLINE")
32 | (description "\
33 | A LONGER DESCRIPTION")
34 | (depends
35 | (ocaml (>= 4.08.0))
36 | ocamlformat
37 | alcotest
38 | testo
39 | )
40 | )
41 |
--------------------------------------------------------------------------------
/proj/proj.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.0.0"
4 | synopsis: "CATCHY HEADLINE"
5 | description: "A LONGER DESCRIPTION"
6 | maintainer: ["NAME "]
7 | authors: ["NAME"]
8 | license: "ISC"
9 | homepage: "https://github.com/USERNAME/PROJ"
10 | bug-reports: "https://github.com/USERNAME/PROJ/issues"
11 | depends: [
12 | "dune" {>= "2.8"}
13 | "ocaml" {>= "4.08.0"}
14 | "ocamlformat"
15 | "alcotest"
16 | "testo"
17 | "odoc" {with-doc}
18 | ]
19 | build: [
20 | ["dune" "subst"] {dev}
21 | [
22 | "dune"
23 | "build"
24 | "-p"
25 | name
26 | "-j"
27 | jobs
28 | "@install"
29 | "@runtest" {with-test}
30 | "@doc" {with-doc}
31 | ]
32 | ]
33 | dev-repo: "git+https://github.com/USERNAME/PROJ.git"
34 |
--------------------------------------------------------------------------------
/proj/sub1/lib/a.ml:
--------------------------------------------------------------------------------
1 | let now () = Unix.gettimeofday ()
2 |
--------------------------------------------------------------------------------
/proj/sub1/lib/a.mli:
--------------------------------------------------------------------------------
1 | (**
2 | This is a sample module.
3 | *)
4 |
5 | val now : unit -> float
6 | (** Returns the time since 1970 in seconds. *)
7 |
--------------------------------------------------------------------------------
/proj/sub1/lib/b.ml:
--------------------------------------------------------------------------------
1 | (*
2 | This is a sample module that depends on the other module Sample_module1.
3 | *)
4 |
5 | open Printf
6 |
7 | let do_something () =
8 | let unixtime = A.now () in
9 | printf "%.3f seconds have elapsed since 1970-01-01T00:00:00.\n%!" unixtime
10 |
--------------------------------------------------------------------------------
/proj/sub1/lib/dune:
--------------------------------------------------------------------------------
1 | ; name = name of the supermodule that will wrap all source files as submodules
2 | ; public_name = name of the library for ocamlfind and opam
3 |
4 | (library
5 | (name proj_sub1)
6 | (public_name proj.sub1)
7 | (libraries unix)
8 | (synopsis "This is a short description of the sub1 library."))
9 |
--------------------------------------------------------------------------------
/proj/sub1/test/a.ml:
--------------------------------------------------------------------------------
1 | (*
2 | Tests for Sub1.A
3 | *)
4 |
5 | let check msg x = Alcotest.(check bool) msg true x
6 |
7 | let test_time () =
8 | check "now is greater than 1000" (Proj_sub1.A.now () > 1000.);
9 | check "now is fix" (Proj_sub1.A.now () > 1_522_882_648.)
10 |
11 | let tests = Testo.categorize "Sub1.A" [ Testo.create "time" test_time ]
12 |
--------------------------------------------------------------------------------
/proj/sub1/test/b.ml:
--------------------------------------------------------------------------------
1 | (*
2 | Tests for Sub1.B
3 | *)
4 |
5 | let test_string () = Alcotest.(check (neg string)) "foo is not bar" "foo" "bar"
6 |
7 | let test_string_hasty () = assert ("foo" <> "bar")
8 |
9 | let tests = Testo.categorize "Sub1.B" [
10 | Testo.create "string" test_string;
11 | Testo.create "string, hasty" test_string_hasty;
12 | ]
13 |
--------------------------------------------------------------------------------
/proj/sub1/test/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name test_sub1)
3 | (libraries alcotest testo proj.sub1)
4 | (synopsis "Tests for sub1"))
5 |
--------------------------------------------------------------------------------
/proj/sub2/bin/bar_main.ml:
--------------------------------------------------------------------------------
1 | ;;
2 | print_endline "Hello! The version is %%VERSION%%"
3 |
--------------------------------------------------------------------------------
/proj/sub2/bin/dune:
--------------------------------------------------------------------------------
1 | ; names = name of the files to compile into executables
2 | ; public_names = name of the executables
3 |
4 | (executables
5 | (names foo_main bar_main)
6 | (public_names sub2-foo sub2-bar)
7 | (libraries proj.sub1 proj.sub2)
8 | (package proj))
9 |
--------------------------------------------------------------------------------
/proj/sub2/bin/foo_main.ml:
--------------------------------------------------------------------------------
1 | (*
2 | Inspect the first command-line argument (Sys.argv.(1))
3 | and determine which subcommand to execute, calling
4 | a function from our library accordingly.
5 | *)
6 |
7 | open Printf
8 |
9 | (*
10 | You can do anything you want.
11 | You may want to use Arg.parse_argv to read the remaining
12 | command-line arguments.
13 | *)
14 | let run _argv_offset = Proj_sub2.A.do_something ()
15 |
16 | let walk _argv_offset = print_endline "Nice."
17 |
18 | (* Add your own subcommands as needed. *)
19 | let subcommands = [ ("run", run); ("walk", walk) ]
20 |
21 | let help () =
22 | let subcommand_names =
23 | String.concat "\n" (List.map (fun (name, _f) -> " " ^ name) subcommands)
24 | in
25 | let usage_msg =
26 | sprintf
27 | "Usage: %s SUBCOMMAND [ARGS]\n\
28 | where SUBCOMMAND is one of:\n\
29 | %s\n\n\
30 | For help on a specific subcommand, try:\n\
31 | %s SUBCOMMAND --help\n"
32 | Sys.argv.(0) subcommand_names Sys.argv.(0)
33 | in
34 | eprintf "%s%!" usage_msg
35 |
36 | let dispatch_subcommand () =
37 | assert (Array.length Sys.argv > 1);
38 | match Sys.argv.(1) with
39 | | "help" | "-h" | "-help" | "--help" -> help ()
40 | | subcmd ->
41 | let argv_offset = 1 in
42 | let action =
43 | try List.assoc subcmd subcommands
44 | with Not_found ->
45 | eprintf "Invalid subcommand: %s\n" subcmd;
46 | help ();
47 | exit 1
48 | in
49 | action argv_offset
50 |
51 | let main () =
52 | let len = Array.length Sys.argv in
53 | if len <= 1 then (
54 | help ();
55 | exit 1)
56 | else dispatch_subcommand ()
57 |
58 | (* Run now. *)
59 | let () = main ()
60 |
--------------------------------------------------------------------------------
/proj/sub2/lib/a.ml:
--------------------------------------------------------------------------------
1 | (*
2 | This is a sample module that depends on the other module Sample_module1.
3 | *)
4 |
5 | open Printf
6 |
7 | let do_something () =
8 | let unixtime = Proj_sub1.A.now () in
9 | printf "%.3f seconds have elapsed since 1970-01-01T00:00:00.\n%!" unixtime
10 |
--------------------------------------------------------------------------------
/proj/sub2/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name proj_sub2)
3 | (public_name proj.sub2)
4 | (libraries unix str proj.sub1)
5 | (synopsis "This is a short description of the sub2 library."))
6 |
--------------------------------------------------------------------------------
/proj/sub2/test/a.ml:
--------------------------------------------------------------------------------
1 | (*
2 | Tests for Sub2.A
3 | *)
4 |
5 | let tests = []
6 |
--------------------------------------------------------------------------------
/proj/sub2/test/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name test_sub2)
3 | (libraries alcotest testo proj.sub2)
4 | (synopsis "Tests for sub2"))
5 |
--------------------------------------------------------------------------------
/proj/test:
--------------------------------------------------------------------------------
1 | _build/default/tests/run_tests.exe
--------------------------------------------------------------------------------
/proj/tests/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name run_tests)
3 | (libraries
4 | ; external libs
5 | testo
6 |
7 | ; local libs
8 | test_sub1
9 | test_sub2
10 | )
11 | )
12 |
--------------------------------------------------------------------------------
/proj/tests/run_tests.ml:
--------------------------------------------------------------------------------
1 | (*
2 | Run all the OCaml test suites defined in the project.
3 | *)
4 |
5 | let tests _env : Testo.t list =
6 | List.flatten [
7 | Test_sub1.A.tests;
8 | Test_sub1.B.tests;
9 | Test_sub2.A.tests;
10 | ]
11 |
12 | let () =
13 | Testo.interpret_argv
14 | ~project_name:"proj"
15 | tests
16 |
--------------------------------------------------------------------------------