Brzo manual
Quick-setting builds – Builds ideas, not scaffolds.
Introduction
Philosophy
brzo
lets you think about your ideas, not the bureaucracy needed to execute them. It is a simple build tool made for exploratory design and learning. It is unsuitable for building software distributions.
With this goal in mind brzo
curtails configuration options and favors heuristics and advices over formal configuration.
If you find yourself fighting with the tool or seeking more control it likely means your ideas have grown into a project that needs a proper build infrastructure. brzo
should be gleefully ditched at that point.
Getting started
In a simple directory structure, a bare brzo
invocation should succeed in building and executing your source files. For example, assuming you have brzo
and an OCaml compiler in your executable path:
> touch BRZO > cat > echo.ml <<EOCAML let echo oc ss = output_string oc (String.concat " " ss ^ "\n") let () = echo stdout (List.tl (Array.to_list Sys.argv)) EOCAML > brzo -- 'Quick!' 'Quick!'
Not very impressive, but illustrates that a bare brzo
invocation goes through the following ordered steps:
- Find the root: starting from the current directory and upwards, stop at the first directory with a
BRZO
file. - Collect source files in the file hierarchy pinned at the root and determine from their extensions a domain. A domain determines a build logic. It usually corresponds to a particular language.
- Using the domain's build logic, compile the source files to an executable outcome artefact.
- Run the outcome artefact with the provided command line arguments.
To understand which root, source files and domain brzo
determines, the following commands can be used:
brzo root # show root directory brzo sources # show source files considered brzo domain # show selected domain
If some directories or files get in the way during source file collection you can include and exclude them with the -i
and -x
options and/or via a BRZO
file:
brzo -x attic # excludes paths prefixed by 'attic' (relative to the root)
Play a bit with the brzo source
command and these flags until you get the selection you desire. Relative paths given to these options are relative to the root.
The relative order between -i
and -x
options is irrelevant, the semantics is that all source file paths whose prefix match one of the -i
options are gathered into a set and then all elements whose prefix match one of the -x
option are removed from it. For example below the -i
option is ineffective in both cases:
brzo -i . -i attic -x attic # excludes 'attic' prefixed paths brzo -i . -x attic -i attic # also excludes 'attic' prefixed paths
In general brzo
tries to be quiet on success so that it doesn't interfere with the standard outputs of the outcome action. However if you want to have a bit more information on stderr
about what brzo
is doing, use the -v
option. Alternatively consult the build log after the build with the log
command.
brzo -v # show build steps on stderr brzo log # show build operations of the last build brzo log --help # see more options to explore the build
brzo
's exit code will be that of your outcome program unless brzo
fails in some way before it gets to execute it. In the latter case the exit code should be attributed and understood as brzo
's one. Exit codes of brzo
and its subcommands are documented in brzo --help
.
To only build the outcome, only perform the outcome action, locate the outcome artefact in the file system, delete the outcome build or see the outcome configuration, use the following invocations:
brzo -b # only build the outcome artefact brzo -a -- 'Quick!' # only perform the outcome action brzo --path # path to the outcome artefact (built or not) brzo -d # delete the outcome build (but not the cache) brzo --conf # show determined configuration
Design documentation
Domains provide a documentation outcome for designing your programs. This outcome builds and shows documentation about the source files; most likely using the domain's language documentation tool. It can be built and run by invoking:
brzo --doc # build and show documentation about source files
Selecting domains and outcomes
You may not always be happy about brzo
's automatic domain selection. Or you may want to switch between different domains in the same root or use domain specific outcomes and options.
In these cases you can specify the domain as the first argument to brzo
or use a BRZO
file.
brzo ocaml -- 'Grunt!' # force the use of the OCaml domain
A domain can provide more options for the default outcomes. For example, the OCaml domain has options to compile the (default) executable outcome to different targets:
brzo ocaml --byte -- 'Quick!' # compile and execute a bytecode program brzo ocaml --html -- 'Quick!' # compile and execute an HTML program brzo ocaml --native -- 'Quick!' # compile and execute a native code program brzo ocaml --node -- 'Quick!' # compile and execute a JavaScript program
Automatic domain selection provides only two build outcomes: --exec
(default) and --doc
. A specific domain can provide more outcomes. For example the OCaml domain has outcomes to run sources in toplevels:
brzo ocaml --top # compile and load in the OCaml toplevel brzo ocaml --top --html # compile and load in the HTML OCaml toplevel brzo ocaml --utop # compile and load in utop
To list available domains, their outcomes and options use:
brzo --help # see the DOMAIN COMMANDS section brzo DOMAIN --help # see the OUTCOMES and OUTCOME OPTIONS section
Cleaning up
At that point you certainly noticed brzo
creates a _b0
directory at the brzo root. This directory has the outcome artefacts in their build directories usually of the form _b0/brzo/DOMAIN-OUTCOME
, a build cache _b0/.cache
used for incremental builds and the (binary) build log _b0/brzo/.log
.
Outcome builds and artefacts can be deleted using the following invocations. Note that this does not delete cached build artefacts.
brzo -d # delete the build of default executable outcome brzo ocaml --html -d # delete the build of the OCaml HTML program outcome brzo delete # delete all outcomes builds (but not the cache)
In general unless you hit a build logic bug (please report) it's better to refrain from deleting the _b0
directory – get rid of "make clean
" habits. If you think the cache is taking too much space use the brzo cache
command to manage it:
brzo cache size # show information about the cache brzo cache gc # delete unused cache keys (fs with hardlink support) brzo cache trim # trim to 50% of the current size brzo cache delete # delete all cache keys (but not the build outcomes)
But if you really want to get rid of the _b0
directory issue:
brzo delete -c # delete (clean) the _b0 directory
Where to go next ?
Back to your ideas.
brzo
strives to provide a hassle and scaffolding free build experience. If it succeeds you should be able to stop here and simply use the tool.
But if you get tired of specifying arguments on the command line you may want to read the section about BRZO
files. You may also want to give a look at configuring your editor.
If a brzo
invocation doesn't succeed or you are puzzled by an outcome result, it may be useful to go through the manual section of the domain you are using. You will get a better understanding on how it works and which domain specific outcomes, options and BRZO file keys it provides. A short list of links to these sections can be found here.
The conceptual overview of the next chapter expands slightly more formally on the material seen in this introduction.
Conceptual overview
Whenever brzo
is executed to perform an outcome it proceeds through the following steps.
- Find the root directory.
- Collect supported source files in the file hierarchy pinned at the root.
- Select the build domain.
- Using domain and outcome specific logic, build an outcome artefact.
- Perform an action on the outcome artefact.
Root
When brzo
is invoked in a directory cwd
, the root is the first directory, starting with cwd
and moving upwards, that has a BRZO
file. If none can be found the tool errors.
Alternatively the root directory can be forced without a BRZO
file via the --root
option or the BRZO_ROOT
environment variable. Invoke brzo root
to find out which root brzo
determines.
Source files
The set of source files collected by brzo
are the files which are deemed useful by any of the domains and whose paths are prefixed by the include prefixes specified by the -i PATH
option and the srcs-i
BRZO
file key. They must also not be prefixed by any of the exclude prefixes specified by the -x PATH
option and the srcs-x
BRZO
file key. If no -i
or srcs-x
is specified the root path is implied.
Relative file paths specified in these options are relative to the brzo root, not relative to the current working directory or BRZO
file. Note that these are prefixes, globs are not supported.
The relative order between -i
and -x
options is irrelevant. The semantics is that all source files from the file system whose prefix match one of the -i
options are collected into a set and then any element of that set whose prefix match any of the -x
option is removed from it. Paths with dotfile, _b0
, or _build
segments are automatically excluded, this can be overriden by specifying them explictely via -i
.
If a -i
is specified on the command line and there's a corresponding path in the srcs-x
key of the BRZO
file, then the latter is removed. Dually if a -x
is present on the command line and there's a corresponding path in the srcs-i
key of the BRZO file, then the latter is removed (but that's by definition of the semantics anyways).
Invoke brzo source
to find out the final source set and brzo
--conf
to find out which includes and excludes are determined from the BRZO
file and the command line.
Domains and selection
Brzo partitions build logics into domains. Domains broadly correspond to build logics for a given language, even though those can overlap, for example when we build mixed OCaml and C programs.
Unless the domain is explicitely specified on the command line by invoking a subcommand like brzo ocaml
or at the root via a BRZO
file, brzo selects a domain according to the following rules, applied in order:
ocaml
domain, if a source file ends with.ml
,.mli
,.mld
,.mll
or.mly
.c
domain, if a source file ends with.h
or.c
.latex
domain, if a source file ends with.tex
,.sty
,.bib
or.bibdoi
.cmark
domain, if a source file ends with.md
.
Invoke brzo domain
to find out which domain brzo
determines.
Outcomes
Each domain has a number of supported build outcomes. An outcome determines a build artefact and an action performed on it.
Every domain defines at least two standard outcomes:
--exec
, an outcomes that builds and runs the simplest expected executable program for the domain. This is the outcome performed on abrzo
,brzo --exec
,brzo DOMAIN
orbrzo DOMAIN --exec
invocation.--doc
, an outcome that produces and shows documentation for the source files considered forexec
, usually using the language's documentation tool. This is the outcome performed on abrzo --doc
orbrzo DOMAIN --doc
invocation. For documentation language domains (cmark
,latex
, etc.) this outcome is often aliased to--exec
.
Domains may provide additional outcomes. Use brzo DOMAIN --help
and the domain specific sections of this manual listed here to learn about them.
Outcome actions
Most outcome actions fall in one of the following categories.
Program execution action
In a program execution action, the action simply runs a built program and gives it the arguments you specify on the brzo
invocation. Use --
to separate execution arguments from those given to brzo
, this allows optional arguments to be interpreted by your program and not by brzo
:
brzo --help # this is brzo's help brzo -- --help # this is your program's help (if programmed to do so)
Browser action
In a browser action, the action usually opens a local file:
URI in your browser. The browser to use can be specified in the BROWSER
environment variable or via the --browser
option.
When the outcome is a web of HTML files, the opened URI is the directory in which these are rooted. brzo
then tries to reload a tab whose address is rooted in the directory's hierarchy rather than the directory itself. The application used to show the URI can be kept in the background with the --background
option.
Sadly, both tab prefix reload and --background
are subject to severe platform limitations, it seems the whole idea of refreshing a tab with a given address from the cli is a total blind spot of browser makers – get in touch if you know lightweight workarounds.
PDF viewer action
In a PDF viewer action, the action opens a PDF file in a viewer. The viewer to use can be specified in the PDFVIEWER
environment variable or via the --pdf-viewer
option.
Editor support
Emacs
If you want to have brzo
as a default compile
command add one of the following to your .emacs
:
(require 'compile) (setq compile-command "brzo -b") ; Globally instead of the default make -k ; Only in a particular mode. Here caml-mode. (add-hook 'caml-mode-hook (lambda () (set (make-local-variable 'compile-command) "brzo -b")))
BRZO files
A BRZO
file determines a root, allows to guide domain specific logic and provides default values. It can be empty just for root marking. BRZO
files can be modified programmatically, see brzo
file --help
for more information.
To disable a BRZO
file at the root use the option --brzo-file
with /dev/null
; doing this does not affect root finding.
A BRZO
file is a sequence of s-expressions. Here's a sample file:
(domain c) ; force the default domain to C (srcs-i src) ; limit source file collection to the src directory (srcs-x src/attic) ; exclude source files from src/attic (ocaml ; OCaml domain specific options (outcome top)) ; force the default outcome to --top
Keys
Relative file paths in the file are always relative to the root, not relative the BRZO
file location – these may not coincide if the --brzo-file
option is used to specify the BRZO
file.
The following global keys are defined:
- (domain DOMAIN) use DOMAIN as the default build domain. Overriden by the first positional argument given to
brzo
, guessed by default. - (srcs-i PATH...) include source files that match given path prefixes (no globs) on the file system. Paths can be removed from the
srcs-i
list from the command line by specifying them via the-x PATH
option. If nothing is specified in the BRZO file and on the command line, defaults to the root path. - (srcs-x PATH...) exclude source files that match given path prefixes (no globs). Paths can be removed from the
srcs-x
list from the command line by specifying them with the-i PATH
option. Paths with dotfile,_b0
or_build
segments are automatically excluded, this can be overriden by explicitly including them viasrcs-i
or-i
. (DOMAIN (KEY ...) ...) configuration key KEY specific to domain DOMAIN. See the domain specific documentation for supported keys. The following keys are supported by all domains:
- (outcome OUTCOME), use OUTCOME as the default build outcome. Overriden by domain selection flags given to
brzo
, defaults toexec
. The values allowed for this field depends on the domain.
- (outcome OUTCOME), use OUTCOME as the default build outcome. Overriden by domain selection flags given to
C domain
The C domain is todo.
Cmark domain
Provides support for handling CommonMark files via the cmark tool.
Exec outcome
Build a web of HTML programs (HTML, JavaScript, CSS) and media files in a directory and open it in the user's browser.
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Any
ROOT/P.ext
source file withext
in the setB0_fexts
.www extension is copied over verbatim to the correspondingBUILD/html/P.ext
path. - Any
ROOT/P.md
file is compiled to aBUILD/html/P.html
file using thecmark
tool. Any.js
and.css
file inBUILD
is linked in the file. The page title is either the basename of the file without the extension or, if that results in"index"
, the basename of the.md
file's parent directory. - Open directory
BUILD
in a browser.
Doc outcome
Aliased to exec outcome.
BRZO file keys
The cmark domain has no specific keys.
LATEX domain
Provides support for handling the LATEX document preparation system. The domain uses the XƎTEX toolchain as it handles Unicode and UTF-8 out of the box; it is distributed with TEX Live.
Exec outcome
Create a PDF document from the LATEX sources and open it in a PDF viewer.
Let ROOT
be the root, R
be the basename of ROOT
, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Any
ROOT/P.bibdoi
file is compiled to aROOT/P.brzo.bib
BibTEX file as described here. This BibTEX file can be included in your.tex
files. Do not edit it manually it will be overwritten bybrzo
. Determine in
ROOT
a mainM.tex
file. This is either, in order:- The file mentioned via the
--main
option - The file mentioned in the BRZO file key latex.main, see here.
- If there's a single
.tex
file, that file - A file whose basename is
main.tex
- A file whose basename is
R.tex
- The file mentioned via the
- Compile
M.tex
toBUILD/M.pdf
usingxelatex
until the fix point is reached. - Open file
BUILD/M.pdf
in a PDF viewer.
Doc outcome
Aliased to exec outcome.
Listing outcome
Creates a PDF document with some of the source files listed by brzo
sources
. The list of supported source extensions is documented in brzo latex --help
under the --listing
option. Sources are assumed to be UTF-8 encoded.
Let ROOT
be the root, R
be the basename of ROOT
, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Generate a document
BUILD/R-listing.tex
which has one section for each source fileROOT/P.ext
withext
in the supported source extensions. Source files are included using the listings package. - Compile
BUILD/R-listing.tex
toBUILD/R-listing.pdf
usingxelatex
until the fix point is reached. - Open file
BUILD/R-listing.pdf
in a PDF viewer.
BRZO file keys
LATEX specific keys are found in the latex dictionary. Here's an example that indicates the main .tex
file is doc/manual.tex
(latex (main doc/manual.tex))
The available keys are:
- (main PATH), specify the main
.tex
file to be PATH. - (doi-resolver URI) use URI to resolve DOIs to BibTEX entries. Defaults to
https://doi.org
.
.bibdoi
files
A .bibdoi
file is a list of DOIs written as a sequence of s-expression atoms. Here's a sample .bibdoi
file:
; Some useful references 10.1145/365230.365257 https://doi.org/10.2307/1968337
As shown above one can specify DOI either by the DOI itself or by an URI in which case the DOI number is taken to be the URI path without the leading /
.
Each DOI D
in the file is resolved to a BibTEX entry using the following HTTP request:
curl -LH "Accept: application/x-bibtex; charset=utf-8" https://doi.org/D
The URI used for the resolving can be changed in the BRZO file.
A P.bibdoi
file defines a corresponding P.brzo.bib
BibTEX file by concatenating the result of resolving each of its DOIs.
OCaml domain
Provides support for handling a set of OCaml, C and JavaScript files.
Exec outcome
Builds source files to an executable and runs it. There are four different outcome targets which define different kind of executables and actions:
--byte
, compile a bytecode executable and run it in the terminal.--html
, compile an HTML program and run it in a browser.--native
, compile a native code executable and run it in the terminal.--node
, compile a JavaScript program and run it in the terminal vianode
.
Unless the target is explicitely specified --native
is used if ocamlopt
is available and --byte
otherwise.
Byte and native code targets
Let BUILD
be the outcome build directory.
- Using best-effort compilation compile sources to objects and link them to a
BUILD/a.out
executable. - Execute
BUILD/a.out
.
HTML target
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Using best-effort compilation compile sources to objects and link them to a
BUILD/a.out
bytecode executable. - Using
js_of_ocaml
compileBUILD/a.out
toBUILD/a.js
. - Compile a shim for the action's environment and arguments to
BUILD/brzo_tty_glue.js
. Action arguments which correspond to files that exist on the file system are crunched to that shim injs_of_ocaml
's virtual file system so that they can be read by the HTML program. - Any
ROOT/P.ext
source file withext
in the setB0_fexts
.www extension is copied over verbatim to the correspondingBUILD/P.ext
. - If there is no
BUILD/index.html
file one is generated with anyBUILD/P.js
andBUILD/P.css
files linked in the file, withbrzo_tty_glue.js
anda.js
comming last. - Open file
BUILD/index.html
in a browser.
Node target
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Using best-effort compilation compile sources to objects and link them to a
BUILD/a.out
bytecode executable. - Using
js_of_ocaml
compileBUILD/a.out
toBUILD/a.out.js
- Any
ROOT/P.js
source file is copied over verbatim to the correspondingBUILD/P.js
file. - Using
jsoo_link
link theBUILD/P.js
files andBUILD/a.out.js
toBUILD/a.js
. - Execute
BUILD/a.js
vianode
.
Doc outcome
Build and show documentation about source files using odoc
.
Let ROOT
be the root, R
be its basename, BUILD
the outcome build directory, P
match file paths and FILE
be the basename of P
.
- Using best-effort compilation produce a
BUILD/cmti/FILE.cmi
files for eachROOT/P.mli
file. This producesBUILD/cmti/FILE.cmti
as a side effect which is resolved for local.odoc
dependencies and compiled toBUILD/odoc/R/FILE.odoc
. - Compile any
ROOT/P.mld
file toBUILD/odoc/R/page-FILE.odoc
. - Compile
ROOT/P.md
files toBUILD/html/_docs/P.html
using thecmark
tool (if available). - If there is no
index.mld
file produce one which links to the.mli
module pages,.mld
pages and the.md
HTML documents and compile it toBUILD/odoc/R/page-index.odoc
- Generate from
.odoc
files the HTML for a package namedR
inBUILD/html
using the user's odig odoc theme if set. - Open directory
BUILD/html/R/
in a browser.
Top outcome
Build source files to object files and load them in the OCaml toplevel. There are four different outcome targets which define different actions:
--byte
, compile to bytecode and load inocaml
.--html
, compile to JavaScript and load in HTML OCaml toplevel.--native
, compile to native code and load inocamlnat
.--node
, unsupported for now.
Unless the target is explicitely specified --native
is used if ocamlnat
is available and --byte
otherwise.
Byte and native code targets
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths and FILE
be the base names of P
.
- Using best-effort compilation compile sources to objects and gather them in a
BUILD/brzo_top.ext
library archive withext
depending on the target. - Execute
ocaml
orocamlnat
withBUILD/brzo_top.ext
, its link dependencies andBUILD
as an include directory. If arguments are specified they are given to the toplevel program invocation.
HTML target
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths. The following steps are taken:
- Using best-effort compilation compile sources to objects and link them to a
BUILD/a.out
bytecode executable. - Using
js_of_ocaml
compileBUILD/a.out
toBUILD/a.js
. - TODO
Utop outcome
Except for the --native
, --html
and --node
targets which are not supported this behaves like the top outcome but loads using the utop tool rather than ocaml
.
BRZO file keys
OCaml specific keys are found in the ocaml dictionary. Here's an example that forces the executable target to html
and constrains library resolution:
(ocaml (target html) ; Default to produce an HTML program (libs ptime/jsoo)) ; Force the use the js_of_ocaml support of ptime
The available keys are:
- (target TARGET), specify either one of byte, html, native or node. Overriden by the
--byte
,--html
,--native
or--node
domain options. - (libs LIB...) add ordered library search restrictions. A
LIB
is a relative path looked up in the root directories ofOCAMLPATH
, see external dependency resolution for details. Libraries specified on the command line via the--lib LIB
option are added in order at the end of this list. - (lock-libs BOOL), specify whether dependency search should be restricted to the dependencies mentioned in libs. Overriden by
--lock-libs=BOOL
option.
Best-effort compilation
Let ROOT
be the root, BUILD
the outcome build directory and P
match file paths and FILE
be the base names of P
. The following steps are taken:
- Module dependencies of
ROOT/P.{mli,ml}
files are resolved and sources are compiled to objectsBUILD/FILE.ext
in the correct order using compilation dependency resolution. ROOT/P.c
C files are compiled toBUILD/FILE.o
- If
ROOT/.merlin
does not exist or exists and starts with"# Generated by brzo"
, write anocamlmerlin
file there according to the dependency resolutions that were found. - According to the outcome proceed to the link phase with all the objects using link dependency resolution.
Note that the build scheme is flat so you cannot have two same FILE
names in different directories of the source tree. If this happens a warning is output and one of the files is arbitrarily dropped.
Best-effort dependency resolution
Compilation dependencies
Given an .ml
or .mli
file SRC
, the set CMIS
of cmi
files that need to be visible to compile SRC
is determined as follows.
ocamldep -modules
is run onSRC
to produce a set of module namesDS
to resolve tocmi
files.Stdlib
is added to this set. Modules namesL
that match OCaml sources in the brzoROOT
are removed fromDS
.- For each of the local resolutions
L
found at the preceeding step addL.cmi
toCMIS
and wait for its compilation. Get all the module names that are defined inL.cmi
and remove these fromDS
. For each remaining unresolved dependency
E
inDS
perform an external dependency resolution for a file namedE.cmi
.- If there a single match add this file to
CMIS
. Get the all the module names that are defined inE.cmi
and remove these fromDS
. - If there's no match or more than one match put the resolution of
E
on hold and proceed with subsequent dependencies to see if it might resolve by later resolutions
Repeat until there is no dependency left or it is not possible to find a
E.cmi
for the remaining ones. In that case if:E.cmi
cannot be found. Then keep the dependency unresolved and proceed.E.cmi
resolves to more than one file. Fail the build and propose a dependency restriction to resolve the ambiguity.
- If there a single match add this file to
- For all files
E.cmi
that were externally resolved andL.cmi
files we then lookup their imported interfaces and perform a resolution for these module references and add them toCMIS
and recursively; otherwise the build may fail because we may be missing type equations.
At the end of this process if there are still remaining unresolved dependencies in DS
we nevertheless proceed to compile with what we have found. If that fails we try to spellcheck these unresolved names with existing cmis in library directories and/or propose opam
packages that could help (if available).
Link dependencies
The set of cmi
files used to compile an object may have been over approximated by compilation dependency resolution. However the OCaml compiler records in the object files exactly which ones were used. Therefore we read back the real compilation dependencies from the compiled objects.
We then hunt for implementations for these dependencies. We do so by performing cmi
file lookups for them. If the file does not resolve locally an external dependency resolution is performed to find the cmi file. Once found this directory is scanned for a library archive that holds a corresponding implementation. If none is found the direct subdirectories are also looked up and if that fails an "mli-only" module is assumed.
Once an archive is selected this lookup procedure needs to be applied on each of the dependencies of the object files it contains and recursively so that linking does not fail.
Eventually this results in correctly sorted local objects and external archives that can be used for the final link step.
External dependencies and restrictions
brzo
assumes external OCaml library archives and objects are installed in root directories specified in the OCAMLPATH
environment variable.
If the variable is undefined it defaults to:
OCAMLPATH=$(opam var lib):$(ocamlc -where) OCAMLPATH=$(opam var lib) # if opam is dirs coincide OCAMLPATH=$(ocamlc -where) # if opam is available
and in this case when ocamlc -where
is used the brzo
maps ocaml
specially to it.
Filenames (e.g. module.cmi
) are looked up on a best-effort basis in all root directories of OCAMLPATH
and their subdirectories without order. In case such a file is found in multiple directories the build fails and brzo
suggests library search restrictions.
Library search restrictions are specified with ordered --lib LIB
options or in the BRZO file. A library LIB
is a relative file path used to order or restrict the search performed in the libraries of OCAMLPATH
.
Let OCAMLPATH=R1:R2
and LIB1
and LIB2
be library restrictions in that order. A library restriction LIB1
has two effects:
- First before starting the build
brzo
checks that at least one ofR1/LIB1
orR2/LIB1
exists. If neither does, the build fails andLIB1
is spellchecked against existing directories inR1
andR2
and/or matched against uninstalledopam
packages to propose anopam
install (if available). Whenever a filename resolution is ambiguous but has a candidate in an existing library restriction then resolution is forced to that candidate. More precisely the candidate that is prefixed by the first of the following paths is taken:
R1/LIB1
R2/LIB1
R1/LIB2
R2/LIB2
Unless you have restrictions for all discovered dependencies, new installs in root directories of OCAMLPATH
may introduce new ambiguities, resulting in builds ambiguously failing while they were previously succeeding. If you want to restrict the search to those directories defined as dependency restrictions use the --lock-libs
option (see corresponding key in the BRZO file).
For example the ptime
package provides multiple implementation for the Ptime_clock
module: one for your operating system and another one for your browser. This results in an ambiguous resolution when Ptime_clock
is used in a source file. These libraries are respectively installed in these directories:
$(opam var lib)/ptime/jsoo $(opam var lib)/ptime/os
In order to force the usage of the operating system clock library use:
brzo ocaml --lib ptime/os
or add the following to the BRZO file:
(ocaml (libs ptime/os))
If you'd rather compile for the browser use:
brzo ocaml --html --lib ptime/jsoo
The BRZO file can be edited programmatically and a failing build may suggest you to do either of:
brzo file set ocaml.libs.v[0] ptime/os # For the OS brzo file set ocaml.libs.v[0] ptime/jsoo # For the browser
which will insert the library restriction at the beginning of the ocaml.libs
dictionary key.
Comments and limitations
Here are a few points that should be taken into account or that could be improved.
- Dependency resolution can be made more precise with a more precise
ocamldep
. For example by keeping track ofopen
s and the order in which modules are mentioned (see for example codept). This would allow to make module name resolution path based (vs. unqualified) and thus more precise. That was the initial plan but eventually we decided to start with the simpler approach outlined above which we suspect is "good enough". The over approximation is harmless and we get to look what was really resolved in the objects for the link step. We also avoid a potential combinatorial explosion. - Module names read in
M.cmi
files do not include those available through external module aliases: these would need further.cmi
files resolution which we avoided for now. We conjecture again this is "good enough". Libraries that do namespacing via modules aliases usually simply entice to open the toplevel namespacing module. As such the module names that are going to be reported byocamldep
will be these module names we will read inM.cmi
. - Dependency wise for compilation, not resolving the external aliase in
M.cmi
to their cmis works as long as those have digested resolutions in the cmi: the stamp ofM.cmi
will change if the digest of the alias changes. - Module implementation resolution assumes all objects are in archives
- C stubs are located where their archive is.
- Variants.
dune
installscmi
s in every variant. Aswell as just above them. `ptime` at the moment does not include the variant independent iface. - Example of compilation dependency over-approximation: the brzo sources themselves are subject to it:
ocamldep
returnsFpath
orFmt
on some of the sources which we resolve to theopam
packagesfmt
andfpath
if they are installed. However once we have compiled when we read back the imported interfaces those are not there because they actually come fromB0_std
. Thus these dependencies are not resoloved or used at link time.