Quickstart ========== Overview -------- :code:`drom` has two purposes: * It's a tool to easily create OCaml projects: :code:`drom new PROJECT` will create a complete OCaml project, with all the required files for :code:`opam` and :code:`dune`, plus additional files for project management on Github, documentation (Sphinx and Github Pages) and CI (Github Actions). * It's a tool to build and install the project, combining calls to `opam` and `dune` to create a local switch, install dependencies, build the project and its documentation :code:`drom` uses subcommands:: $ drom DROM(1) Drom Manual DROM(1) NAME drom - Create and manage an OCaml project SYNOPSIS drom COMMAND ... COMMANDS build Build a project build-deps Install build dependencies only clean Clean the project from build files dev-deps Install dev dependencies (odoc, ocamlformat, merlin, etc.) doc Generate library API documentation using odoc in the docs/doc directory fmt Format sources with ocamlformat help display help about drom and drom commands install Build & install the project in the project opam switch package Create or update a package within a project project Create or update a project publish Generate a set of packages from all found drom.toml files run Execute the project sphinx Generate general documentation using sphinx test Run tests tree Display dependencies in the project as a tree uninstall Uninstall the project from the project opam switch update Update packages in the project opam switch COMMON OPTIONS --help[=FMT] (default=auto) Show this help in format FMT. The value FMT must be one of `auto', `pager', `groff' or `plain'. With `auto', the format is `pager` or `plain' whenever the TERM env var is `dumb' or undefined. --version Show version information. Creating a Project ------------------ Let's suppose we want to create a new project :code:`hello_world`. We can use `drom` to create almost everything we need for that! Let's create the project:: $ drom new hello_world Creating project "hello_world" with skeleton "program", license "LGPL2" and sources in src/hello_world: Creating directory hello_world Calling git init Initialized empty Git repository in /home/lefessan/tmp/hello_world/.git/ Calling git remote add origin git@github.com:ocamlpro/hello_world Calling git commit --allow-empty -m Initial commit [master (root-commit) 732c93c] Initial commit Creating file dune-project Creating file src/hello_world/index.mld Creating file hello_world.opam Creating file src/hello_world_lib/version.ml Creating file src/hello_world_lib/index.mld Creating file hello_world_lib.opam Creating file sphinx/_static/css/fixes.css Creating file test/output-tests/test2.ml Creating file test/output-tests/test2.expected Creating file test/output-tests/test1.expected Creating file test/output-tests/dune Creating file test/inline-tests/test.ml Creating file test/inline-tests/dune Creating file test/expect-tests/test.ml Creating file test/expect-tests/dune Creating file .github/workflows/workflow.yml Creating file .github/workflows/doc-deploy.yml Creating file docs/sphinx/index.html Creating file docs/doc/index.html Creating file sphinx/license.rst Creating file sphinx/install.rst Creating file sphinx/index.rst Creating file sphinx/conf.py Creating file sphinx/about.rst Creating file docs/style.css Creating file docs/index.html Creating file docs/favicon.png Creating file docs/README.txt Creating file dune Creating file .ocp-indent Creating file .ocamlformat-ignore Creating file .ocamlformat Creating file .gitignore Creating file README.md Creating file Makefile Creating file LICENSE.md Creating file CHANGES.md Creating file src/hello_world/main.ml Creating file src/hello_world/dune Creating file src/hello_world_lib/main.ml Creating file src/hello_world_lib/dune Forced Update of file drom.toml Forced Update of file src/hello_world_lib/package.toml Forced Update of file src/hello_world/package.toml Calling git add .drom test/output-tests/test2.ml test/output-tests/test2.expected test/output-tests/test1.expected test/output-tests/dune test/inline-tests/test.ml test/inline-tests/dune test/expect-tests/test.ml test/expect-tests/dune src/hello_world_lib/version.ml src/hello_world_lib/package.toml src/hello_world_lib/main.ml src/hello_world_lib/index.mld src/hello_world_lib/dune src/hello_world/package.toml src/hello_world/main.ml src/hello_world/index.mld src/hello_world/dune sphinx/license.rst sphinx/install.rst sphinx/index.rst sphinx/conf.py sphinx/about.rst sphinx/_static/css/fixes.css hello_world_lib.opam hello_world.opam dune-project dune drom.toml docs/style.css docs/sphinx/index.html docs/index.html docs/favicon.png docs/doc/index.html docs/README.txt README.md Makefile LICENSE.md CHANGES.md .ocp-indent .ocamlformat-ignore .ocamlformat .gitignore .github/workflows/workflow.yml .github/workflows/doc-deploy.yml As you can see, :code:`drom` created a directory :code:`hello_world` with the following files: * :code:`drom.toml` for project management by :code:`drom`, and two files :code:`package.toml` for each sub-package in their sources. * Source files for the project, composed of an :code:`hello_world_lib` library in :code:`src/hello_world_lib/` and a driver executable in :code:`src/hello_world/main.ml` * :code:`git` source version management files: :code:`.gitignore` and :code:`.git/` for the :code:`git` revision control tool * Documentation files: :code:`docs/index.html` and :code:`docs/style.css` for the project homepage * :code:`sphinx/` directory for the Sphinx documentation formatter * :code:`README.md`, :code:`CHANGES.md` and :code:`LICENSE.md` project files * Specific files for :code:`opam` and :code:`dune`: :code:`dune-project`, :code:`hello_world.opam` and `src/hello_world/dune` for example * :code:`.github/` for Github Actions CI * :code:`.ocamlformat` for the :code:`ocamlformat` code formatting tool and :code:`.ocp-index` for source lookups * Examples of test files in the :code:`test/` directory At this point, you may decide that :code:`drom` has done enough for you, and you can go back to using :code:`opam` and :code:`dune` to work on your project. The :code:`drom.toml` file has a particular importance, it can be used by :code:`drom` to update all the generated files with this information. It contains information on the project, such as its name, license, description, dependencies, etc. Everytime you modify :code:`drom.toml`, you should call :code:`drom project` again to update the project:: $ cd hello_world $ emacs drom.toml $ drom project drom: Entering directory '/tmp/hello_world' Updating file dune-project Updating file hello_world.opam Updating file hello_world_lib.opam Updating file src/hello_world/dune Updating file src/hello_world_lib/dune Calling git add .drom src/hello_world_lib/dune src/hello_world/dune hello_world_lib.opam hello_world.opam dune-project . Here, we added a dependency in the `drom.toml` file:: ... [dependencies] ez_file = "" ... And we see that :code:`drom` updated the files for :code:`dune` and :code:`opam`. :code:`drom project` also takes a few command line options that can be used to modify the :code:`drom.toml` file: * :code:`--skeleton SKELETON` can be used to change the skeleton used to manage files. :code:`drom` knows about 3 differents skeletons by default: :code:`library` (a simple library), :code:`program` (a driver calling a library, the default skeleton used when nothing is specified) and :code:`virtual` (no default package). An up-to-date list of project skeletons can be found in the generated file :code:`_drom/known-skeletons.txt`. * :code:`--upgrade` can be used to upgrade the :code:`drom.toml` file when you are using a more recent version of :code:`drom` * :code:`--binary` and :code:`--javascript` can be used to switch between generating binaries and generating Javascript using :code:`js_of_ocaml` by default. Notice that, just after creating the project, you should be able to build it and run it with no error! Building a Project ------------------------------------ :code:`drom` can be used to build a project. In this case, it will use :code:`opam` to manage the environment (dependencies) and :code:`dune` to build the project. You don't need to know these tools for basic usage of :code:`drom`. Because :code:`drom` makes extensive use of local :code:`opam` switches, it is a good idea to use it from :code:`opam-bin` to benefit from binary caching of packages, to speedup creation of local switches. Building locally ~~~~~~~~~~~~~~~~ By default, :code:`drom` will try to build the project in its directory:: $ cd hello_world $ drom build -y Calling opam switch create -y . --empty Calling opam install -y ocaml.4.10.0 The following actions will be performed: ∗ install base-bigarray base ∗ install base-threads base ∗ install base-unix base ∗ install ocaml-base-compiler 4.10.0 [required by ocaml] ∗ install ocaml-config 1 [required by ocaml] ∗ install ocaml 4.10.0 ===== ∗ 6 ===== <><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><> [ocaml-base-compiler.4.10.0] found in cache <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ∗ installed base-bigarray.base ∗ installed base-threads.base ∗ installed base-unix.base ∗ installed ocaml-base-compiler.4.10.0 ∗ installed ocaml-config.1 ∗ installed ocaml.4.10.0 Done. # Run eval $(opam env) to update the current shell environment Calling opam switch set-base ocaml Calling opam install -y --deps-only ./_drom/new.opam The following actions will be performed: ∗ install dune 2.7.0 <><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><> [dune.2.7.0] found in cache <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ∗ installed dune.2.7.0 Done. # Run eval $(opam env) to update the current shell environment Calling opam exec -- dune build Done: 40/46 (jobs: 1) Build OK During this build, :code:`drom` performed the following operations: * It loads the project definition file :code:`drom.toml` * It creates a local :code:`opam` switch (directory :code:`_opam`) where it installs the version of OCaml specified in the :code:`edition` field of the project definition * It installs all the dependencies of the package. In our simple example, it is only the :code:`dune` build tool. * Once the environment is ok, it builds the project using :code:`dune`. Since building the environment can take some time, it is important to know that it is only done the first time. It will also be upgraded only if the dependencies are changed. We can now run the program:: $ drom run In opam switch /tmp/hello_world/_opam Calling opam exec -- dune build Done: 0/0 (jobs: 0) Calling opam exec -- dune exec -p hello_world -- hello_world Hello world! It's a bit verbose, but the last line :code:`Hello world!` was printed by our project! Building with a global :code:`opam` switch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :code:`drom` can use global switches also. For example, if you want to install the project in that switch:: $ drom build --switch 4.10.0 Error: You must remove the local switch `_opam` before using option --switch Since we previously built the project locally, we have a local :code:`_opam` switch. :code:`drom` will not remove this switch automatically, because it is often long to rebuild. So, you will have to do it yourself (or backup it if you are not using :code:`opam-bin`):: $ rm -rf _opam $ drom build --switch 4.10.0 Calling opam switch link 4.10.0 Directory /tmp/hello_world set to use switch 4.10.0. Just remove /tmp/hello_world/_opam to unlink. In opam switch 4.10.0 Calling opam install --deps-only ./_drom/new.opam Nothing to do. # Run eval $(opam env) to update the current shell environment Calling opam exec -- dune build Build OK :code:`drom` performed exactly the same steps as for a local build. In our case, the :code:`opam` switch :code:`4.10.0` already existed on our computer, and the dependencies were already installed, so it only built the project. However, if the switch specified by :code:`--switch` does not exist globally, :code:`drom` will call :code:`opam` to create it. Installing the Project ~~~~~~~~~~~~~~~~~~~~~~ Now that we have tested that our project correctly builds in a local switch and in a global switch, we can ask :code:`drom` to install it in the switch:: $ drom install Directory /tmp/hello_world set to use switch 4.07.0. Just remove /tmp/hello_world/_opam to unlink. In opam switch 4.07.0 Calling opam install --deps-only ./_drom/new.opam Nothing to do. Calling opam exec -- dune build Calling opam uninstall -y hello_world The following actions will be performed: ⊘ remove hello_world 0.1.0 <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ⊘ removed hello_world.0.1.0 Done. Calling opam pin -y --no-action -k path . Package hello_world does not exist, create as a NEW package? [Y/n] y [hello_world.~dev] synchronised from file:///tmp/hello_world hello_world is now pinned to file:///tmp/hello_world (version 0.1.0) Calling opam install -y hello_world <><> Synchronising pinned packages ><><><><><><><><><><><><><><><><><><><><><><> [hello_world.0.1.0] no changes from file:///tmp/hello_world The following actions will be performed: ∗ install hello_world 0.1.0* <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ∗ installed hello_world.0.1.0 Done. Calling opam unpin -n hello_world Ok, hello_world is no longer pinned to file:///tmp/hello_world (version 0.1.0) Installation OK As we can see in this example, :code:`drom` performed the following steps: * Building the project, as in the previous sections * Removing all the packages of the project that may already be installed * Pinning all the packages for :code:`opam` * Installing the pinned packages (rebuilding them in :code:`opam`) * Unpinning all the packages Building Documentation ---------------------- :code:`drom` generates a web-site for your project with 2 parts that you need to generate separately: a documentation of the library API automatically generated by :code:`odoc` and a general documentation that you can modify, generated by the `sphinx-doc tool `__, with the `Read-the-doc theme `__ . You will need to install them, it's usually something like:: pip install sphinx pip install sphinx_rtd_theme The documentation is generated in the :code:`_drom/docs/` directory. If you are on Github, `drom` generates a Github action that will automatically merge this directory into the :code:`gh-pages` branch after every merge/push in the :code:`master` branch, so that you can easily use Github Pages to host your project documentation. The main webpage is created from :code:`docs/index.html`, and the Sphinx files used to generate the documentation are in :code:`sphinx/`. You can edit these files before generating the documentation. To generate the documentation, you must call two commands: * :code:`drom odoc`, each time you want to generate the documentation of the API * :code:`drom sphinx`, each time you want to compile the Sphinx files The command :code:`drom doc` can be used to generate everything. You can use these commands with an extra argument :code:`--view` to open a local browser on the documentation. Since generating the API documentation requires to use :code:`odoc`, :code:`drom` will automatically install the Development dependencies of your project. They are usually tools like :code:`merlin`, :code:`odoc` or :code:`ocamlformat` that only developers will need. Still, you can trigger directly their installation using:: $ drom dev-deps In opam switch 4.10.0 Calling opam install odoc ocamlformat The following actions will be performed: ∗ install odoc 1.5.1 ∗ install ocamlformat 0.15.0 ===== ∗ 2 ===== <><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><> [ocamlformat.0.15.0] found in cache [odoc.1.5.1] found in cache <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ∗ installed odoc.1.5.1 ∗ installed ocamlformat.0.15.0 Done. In our case, only :code:`odoc` and :code:`ocamlformat` have been detected as missing, so they are installed. Let's now generate the API documentation:: $ drom odoc In opam switch 4.10.0 Calling opam exec -- dune build Calling opam exec -- dune build @doc Calling rsync -auv --delete _build/default/_doc/_html/. _drom/docs/doc sending incremental file list ./ highlight.pack.js index.html odoc.css hello_world/ hello_world/index.html sent 26,886 bytes received 103 bytes 53,978.00 bytes/sec total size is 26,507 speedup is 0.98 The API documentation has been generated and copied into :code:`_drom/docs/doc`. Let's now generate the Sphinx documentation:: $ drom sphinx Calling sphinx-build sphinx _drom/docs/sphinx Running Sphinx v1.8.5 building [mo]: targets for 0 po files that are out of date building [html]: targets for 4 source files that are out of date updating environment: 4 added, 0 changed, 0 removed /tmp/hello_world/sphinx/index.rst:8: WARNING: Title underline too short. Welcome to hello_world doc ================= looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [100%] license generating indices... genindex writing additional pages... search copying static files... done copying extra files... done dumping search index in English (code: en) ... done dumping object inventory... done build succeeded, 1 warning. The HTML pages are in _drom/docs/sphinx. We can now check how it looks like:: $ xdg-open ./_drom/docs/sphinx/index.html