├── .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 | --------------------------------------------------------------------------------