Contacts: @julow @jonludlam @lortex
This document describes how we build and update the package docs on www.ocaml.org
.
The documentation is produced from the built libraries using odoc
, a tool that is designed and built from the ground up to understand how OCaml libraries are assembled and used. It allows linking between different libraries in different packages, ensuring that links end up at exactly the correct place, a task complicated by the fact that this depends upon the precise versions of all dependent packages required to build the package being documented.
These documents will be placed in the ocaml.org website combined with the information currently shown on https://opam.ocaml.org/packages/. This will be the canonical source of information of packages published in opam.
Building package docs with ocaml-docs-ci and Voodoo is an incremental process, and as new packages are added to opam repository
the work required is restricted to the new package only. Occasionally we may rebuild large chunks of the website, for example when a new OCaml release is made, but updating the website for new packages should be a quick process.
Stages of the process:
opam-repository
This is an ocurrent pipeline, using an ssh server to contain the generated artifacts.
voodoo-prep
:
prep/universes/<universe_id>/<package name>/<package version>
directory. It's given a list of packages to prep, and the associated universe id.voodoo-do
:
compile/universes/<universe_id>/<package name>/<package version>/
: odoc and odocl fileshtml/universes/<universe_id>/<package name>/<package version>/
: html filescompile/packages/<package name>/<package version>/
html/packages/<package name>/<package version>/
voodoo-indexes
:
A package in not just defined by the tuple of package name and package version. Additionally, it may be dependent on any of the packages it depends upon - for example, consider a package containing an mli file such as:
The expansion of this will depend on which version of the standard library it was compiled against.
A particular package is therefore specified by the triple of the package name, the package version, and the 'dependency universe hash'. This has is computed in the following way:
<package name>.<version>
For example:
which are the dependencies on this particular system for the package astring.0.8.5
. The hash of this should be 92edc0c1c4ec93b2f61fdd7fc9491460
The type to uniquely identify a package is therefore given by:
Because odoc handles include paths in the same way that OCaml does, and because we would like references to behave in the same familiar way that normal OCaml paths do, it makes sense to keep the odoc
files in the identical directory structure to that of the associated cmt
, cmti
and cmi
files. This does not imply that the directory structure of the output html
files (or man/latex files) must mirror this. The implication of this is that we cannot determine sub-packages.
As an example of the various ways complex packages are layed out, we have the following case studies:
~/.opam/$switch/lib/plugin-loader
and the other in ~/.opam/$switch/lib/oasis
"common.cma algo.cma versioning.cma pef.cma debian.cma csw.cma opam.cma npm.cma"
algo.cma
ocamlfind
Questions:
What do we do for something like dose3?
What do we do for the OCaml libraries (stdlib, seq, raw_spacetime, str etc โ these don't have opam packages โ mostly the META files come from the ocamlfind
package)
What other packages will be painful? We have the 'corpus' compiled already, but missing files like META, dune-packages and so on.
We should detect sub-packages and group modules under them in package pages. This is an important information to be able to use them in Dune for example.
lib/*
directories and two META
files (oasis).
Maybe:
.changes
files and treating the second package as a sub-package ?Reliable ways to find them:
Querying ocamlfind is only way to pair sub-package names with archives. The library exposes a parser for META files.
Currently, the CLI fails to print archives, for example %A
is not working in ocamlfind query -format "%p %d %A"
Later, we'll also need assemble
to create a nice looking hierarchy for sub-packages,
for example: .../<package>/<version>/<sub.package>/<modules>
Most packages don't have documentation pages but have:
doc/$/README.*
(.org or .md)
doc/$/LICENSE.*
doc/$/CHANGES.*
lib/$/META
Every packages except the stdlib have it.
It's the only way to know sub packages.
lib/$/opam
Added by opam.
lib/$/dune-package
Added by dune, only in projects using dune.
Contains the same informations as META
.
lib/$/**.ml?
Source files, intended to be seen by merlin or why not, "see code" links from documentation. (Odoc should do that someday !)
A few packages have documentation intended to be read by Odoc:
doc/$/odoc-pages/index.mld
This is intended to be the entry point of the package's doc.
assemble
should use it has the package page, possibly modifying it to add a common header.doc/$/odoc-pages/*.mld
Various other files we can find sometimes:
doc/$/*.ml
In dbuezli's libraries, it is meant to be appended at the end of index.mld
automatically.share/$
But these are not intended to be read by users (eg. emacs/vim plugins)voodoo-prep
adds some other informations that may be useful:
This is the incremental pipeline to build documentation.
Repo: https://github.com/ocurrent/ocaml-docs-ci
Given an opam repository commit, list all its packages.
An incremental solver, performing opam-0install solves when new packages are added. After the solving step, we obtain a list of Package.t
which corresponds to packages and their associated universes.
From the list of packages to obtain, generate a list of prep jobs to perform. Each job consists in a single package to install and multiple packages to prep.
Done via ocluster.
Perform the prep step for one job. It will generate the prep folder of multiple packages. The Folder_digests.t
value allows to track existing prep folders.
Prep data is stored in /prep/universes/<universe>/<name>/<version>/
.
Should be updated when:
/prep
folder digest is invalidatedDone via ocluster. Compile .odoc, .odocl and .html files for one package, given its prep result and the compile result of its dependencies.
Output (when blessed, otherwise replace packages
by universes/<universe_id>
):
/compile/packages/<name>/page-<version>.odoc(l)
/compile/packages/<name>/<version>/
/html/packages/<name>/<version>/
Given the list of the successfully compiled packages, generate the index pages and compile them to HTML. Done on the host machine.
Output:
/html/packages/index.html
/html/packages/<name>/index.html
/html/universes/index.html
/html/universes/<universe_id>/index.html
Current repo: https://github.com/ocaml-doc/voodoo
The job as submitted by the pipeline will install a specific set of packages. Once the install has completed, voodoo-prep, the binary, will be executed.
Voodoo-prep is run after the build of a particular set of packages has been completed. It is run in the environment in which the build succeeded.
We iterate through all of the packages installed in the opam environment, and go through the files installed as part of each package, as recorded by ~/.opam/<switch>/.opam-switch/install/<package>.changes
. The tool collects the following types of files:
File type | Reason |
---|---|
.cmti | This is what odoc would prefer to operate on. |
.cmt | Odoc will use cmt files for analysing usage of identifiers. |
.cmi | Only if the above two files don't exist will odoc resort to using cmi files. |
.mld, .md, examples, contents of doc opam dir |
These are documentation files. |
.cm(x)a info, dune-package, META | voodoo-do may use the info from these files to organise the documentation into libraries/subpackages. |
a | |
All of the above files are copied into the following path: |
where the ...
represents the identical path the files appear under ~/.opam/<switch>/
. hash, package and version are the triple that uniquely identifies a package as described above.
The info contained in cmxa/cma
libraries installed as part of the package is collected as follows:
The opam file is collected from ~/.opam/<switch>/.opam-switch/packages/<package_name>.<package_version>/opam"
The contents of this file will be rendered when someone visits the URL http://docs.ocaml.org/packages/$package/$version/
and is therefore the landing page for the package as a whole. As such it needs to contain all the important info needed. It should contain:
The package may contain an index.mld
file. It must be concatenated at the end of version.mld
with its level-0 headings removed.
This way, every package pages share a common header.
Examples follow:
There is one section for every sub-packages, using findlib's name, containing the lists of modules. See Detecting sub-packages above.
Suggested layout:
For example for yaml
:
The example above is making an exception for the "main" library. This is done only if that library have the same name as the Opam package. (common under Dune)
assemble
compute them
These two are currently simply blacklisted and removed from universe calculations
These currently don't produce anything but add many extra steps. Should they be blacklisted?
Currently Stdlib docs are under ocaml-base-compiler - we probably want them under 'ocaml' instead? or maybe elsewhere?
Extra click in wrapped libraries Some libraries have one top-level module, which has the whole library as submodules. This module is generated by Dune and is often not very useful in the doc, it's an unordered list of modules. We could inline it into the package page and avoid an unecessary click. Some packages document this module carefully and it sometimes contains types and values (for example base).
weird packages:
topdirs.cmti
in 2 places โ lib/ocaml and lib/ocaml/compiler-libs/