dune

The selector named (dune _) gives you access to a sub hierarchy of selectors dedicated to stanzas and fields found in dune files.

In this document you'll find the list of all selectors and predicates that live inside the (dune _) hierarchy, along with their meaning and some examples.

executable

(dune (executable _)) is a selector for the dune top level stanza named executable:

Stanza:

(executable <FRAGMENT>)

Warning: Beware, although this selector share sub selectors with the executables stanzas, its use only selects the executable stanza singular.

name

(dune (executable (name _))) is a selector for the name field of an executable stanza:

Stanza:

(executable
 (name <FRAGMENT>))

Its predicates are:

  1. (equals NAME)

Returns true iif the NAME supplied is an exact match for the name present in the FRAGMENT.

When enforced, dunolint suggests to replace any existing name with the one specified by the predicate. Doesn't support suggestion when negated.

  1. (is_prefix PREFIX), (is_suffix SUFFIX)

Returns true iif the name has the given prefix (resp. suffix).

When enforced, dunolint suggests prepending or appending the supplied value directly to the existing name, with no special heuristics (it doesn't try to be smart about completing partial prefixes, etc.). You may adjust manually as you see fit.

Negation: When the negation of the predicates is_prefix and is_suffix is enforced, dunolint suggests removing the supplied value directly from the name if applicable, if it is currently a prefix (resp. suffix).

Absent field: Only (equals NAME) can initialize an absent field. Predicates like (is_prefix _) and (is_suffix _) cannot provide an initial value, so enforcing them on an absent field will fail. Use (equals NAME) to initialize the field, or add it manually before running dunolint.

Examples:

Stanza with name present:

(executable
 (name main))

Condition: (dune (executable (name PREDICATE)))

PredicateResult
(equals main)True
(is_prefix "my_")False. Suggestion: "my_main"
(is_suffix "_test")False. Suggestion: "main_test"

Stanza with name absent:

(executable
 (public_name my-cli))

Condition: (enforce (dune (executable (name PREDICATE))))

PredicateResult
(equals main)Field inserted: (name main)
(is_prefix "my_")Enforcement failure
(is_suffix "_test")Enforcement failure

public_name

(dune (executable (public_name _))) is a selector for the public_name field of an executable stanza:

Stanza:

(executable
 (public_name <FRAGMENT>))

It is almost identical to its name sibling, thus we are not documenting it in details here. A notable difference is that the suggestions for the (is_prefix my-package.) predicate is improved when the prefix is a package name, as dunolint will indeed suggests to replace an existing package prefix if one if present. This is a minor ergonomic detail.

libraries

(dune (executable (libraries _))) is a selector for the libraries field of an executable stanza:

Stanza:

(executable
 (libraries <FRAGMENT>))

It is identical to the library (libraries) selector. See that section for details on predicates and examples.

Fields shared with other stanzas

This stanza share some sub selectors with other stanzas. See: has_field, instrumentation, libraries, lint, preprocess.

For example, you can use the (dune (executable (instrumentation _))) syntax if you want the instrumentation selector to apply to the executable stanza only.

executables

Warning: The (executables _) stanzas are currently not handled, and are ignored by dunolint. We have plans to fix this early on.

has_field

(dune (has_field FIELD)) is a predicate for dune stanzas. If the provided field is applicable to the a given stanza type, then it returns true iif the stanza has such field in the dune file, regardless of its contents. When the supplied FIELD doesn't apply to the stanza currently linted, this returns undefined.

Supported FIELDsApplicable Stanzas
instrumentationexecutable, library
lintexecutable, library
nameexecutable, library
preprocessexecutable, library
public_nameexecutable, library

When enforced, dunolint suggests initializing the field if a default value can be inferred from context. Otherwise dunolint will request that you handle the linting error manually.

Negation: When the negation of the predicate has_field is enforced, dunolint suggests removing the field entirely.

Examples:

Stanza:

(library
 (name dunolint-lib)
 (instrumentation (backend bisect_ppx)))

Condition: (dune (library PREDICATE))

PredicateResult
(has_field instrumentation)True
(has_field preprocess)False. Suggestion: initialize the field
(not (has_field instrumentation))False. Suggestion: remove the field

if_present

(if_present (FIELD CONDITION)) is a conditional wrapper that applies a field predicate only when the field is present. If the field is absent, the condition evaluates to true and enforcement is skipped (unapplicable).

This is useful when you want to enforce constraints on a field only if it exists, without failing when the field is absent.

Supported FIELDs:

FieldStanza
public_namelibrary
packagelibrary

Behavior:

  • Eval: If the field is absent, returns true. If the field is present, evaluates the inner condition.
  • Enforce: If the field is absent, does nothing (unapplicable). If the field is present, enforces the inner condition.

Tip: Use if_present when you want a constraint to apply only to existing fields without trying to create them. This is particularly useful for enforcing naming conventions like prefixes or suffixes on optional fields.

Examples:

Stanza without public_name:

(library
 (name mylib))
ConditionResult
(if_present (public_name (is_prefix "lib.")))True (field absent, so condition is satisfied)
(public_name (is_prefix "lib."))Undefined (field absent)
(enforce (if_present (public_name (is_prefix "lib."))))No change (unapplicable)
(enforce (public_name (is_prefix "lib.")))Enforcement failure

Stanza with public_name:

(library
 (name mylib)
 (public_name my-public-lib))
ConditionResult
(if_present (public_name (is_prefix "lib.")))False. Suggestion: "lib.my-public-lib"
(if_present (public_name (is_prefix "my-")))True

Use case in config:

;; Apply prefix constraint only when public_name exists
(rule
 (cond
  ((path (glob src/**))
   (enforce
    (dune
     (library
      (if_present (public_name (is_prefix "mylib.")))))))))

Compare with the alternative using if_ and has_field:

;; Equivalent behavior using if_ and has_field
(rule
 (cond
  ((path (glob src/**))
   (enforce
    (dune
     (library
      (if_ (has_field public_name)
           (public_name (is_prefix "mylib."))
           true_)))))))

include_subdirs

(dune (include_subdirs _)) is a selector for the dune top level stanza of the same name.

Stanza:

(include_subdirs FRAGMENT)

Its predicates are:

  1. (equals MODE)

Returns true iif the MODE supplied is an exact match for the mode present in the FRAGMENT.

MODE := no | qualified | unqualified

When enforced, dunolint suggests to replace any existing mode with the one specified by the predicate. Doesn't support suggestion when negated.

Examples:

Stanza:

(include_subdirs qualified)

Condition: (dune (include_subdirs PREDICATE))

PredicateResult
(equals qualified)True
(equals unqualified)False. Suggestion: unqualified

instrumentation

(dune (instrumentation _)) is a selector for the instrumentation fields found in stanzas library, executable and executables.

A stanza may have several instrumentation fields, one per backend:

(library
 (instrumentation <FRAGMENT>)
 (instrumentation <FRAGMENT>))

The selector considers these fields as a collection: its predicates are about the backends of the stanza, rather than about one field in particular.

Its predicates are:

  1. (backend NAME FLAGS...)

Returns true iff there is a field with the backend NAME, and at least the flags supplied, in any order. The field may have other flags. Without flags, this returns true iff there is a field for the backend NAME, regardless of its flags.

Some backends require additional flags. For example, ppx_windtrap uses the --coverage flag:

(instrumentation
 (backend ppx_windtrap --coverage))

When enforced, dunolint suggests adding the missing flags to the field that has the backend NAME, after its existing flags. If there is no such field, it suggests adding an (instrumentation (backend NAME FLAGS...)) field. Fields with other backends are left untouched, in particular they are never renamed: see Migrating to another backend.

Negation of backend:

  • Without flags, (not (backend a)) is equivalent to (absent a), and is enforced as such.
  • With flags, the negation is only checked, and an enforcement failure is reported when it doesn't hold. This is the same as for the pp_with_flag predicate of the pps selector.
  1. (present BACKEND_NAMES)

Returns true iff there is a field for each of the backends specified, regardless of their flags.

When enforced, dunolint suggests adding an (instrumentation (backend NAME)) field for each backend not already present.

  1. (absent BACKEND_NAMES)

Returns true iff there is no field for any of the backends specified.

When enforced, dunolint suggests removing the fields of these backends.

Negation of present and absent: They follow the same rules as for the libraries selector: with a single backend, they are enforced as their opposite, and with several backends, they are only checked.

Prefer absent over a negated present, and present over a negated absent: (absent a b) requires that there is no field for any of these backends, and (present a b) that there is one for each of them, which dunolint can always enforce.

Empty arguments: present and absent expect at least one backend name.

Semantics:

Let F be the set of instrumentation fields of the stanza, where each field f has a backend name(f) and a set of flags flags(f). The predicates are defined as:

  • (backend n x1 ... xk) holds iff ∃ f ∈ F, name(f) = n ∧ {x1, ..., xk} ⊆ flags(f)
  • (present n1 ... nk) holds iff ∀ i, ∃ f ∈ F, name(f) = ni
  • (absent n1 ... nk) holds iff ∀ i, ∄ f ∈ F, name(f) = ni

The boolean constructs and, or, not and if combine these as usual, all of them being evaluated against the same set F. For example, (and (backend a) (backend b)) holds when the stanza has a field for a and another one for b.

When the stanza has no instrumentation field, F is empty: backend and present are false, and absent is true. Thus the conditions are evaluated even when there is no such field, and enforcing backend or present adds the field. Separately, enforcing (has_field instrumentation) adds an (instrumentation (backend bisect_ppx)) field.

Enforcement: The predicates of a condition are enforced one after the other, as described above. When a condition cannot be enforced, the failure is reported once for the whole condition, and the fields are left unchanged.

Examples:

Stanza:

(library
 (instrumentation (backend bisect_ppx))
 (instrumentation (backend landmarks)))

Condition: (dune (library (instrumentation PREDICATE)))

PredicateResult
(backend bisect_ppx)True
(backend bisect_ppx --flag)False. Suggestion: add --flag to the bisect_ppx field
(backend other)False. Suggestion: add (instrumentation (backend other))
(present bisect_ppx landmarks)True
(present bisect_ppx other)False. Suggestion: add (instrumentation (backend other))
(absent other)True
(absent landmarks)False. Suggestion: remove the landmarks field
(not (backend landmarks))False. Suggestion: remove the landmarks field
(not (present bisect_ppx landmarks))Enforcement failure: removing either field would do

Stanza with backend flags:

(library
 (instrumentation (backend ppx_windtrap --coverage)))

Condition: (dune (library (instrumentation PREDICATE)))

PredicateResult
(backend ppx_windtrap)True
(backend ppx_windtrap --coverage)True
(present ppx_windtrap)True
(backend bisect_ppx)False. Suggestion: add (instrumentation (backend bisect_ppx))
(not (backend ppx_windtrap --coverage))Enforcement failure: the negation of backend with flags is only checked

Migrating to another backend

Linting rules never rename the backend of a field. To migrate from a backend to another, combine a condition requiring the new backend with a condition forbidding the old one:

(rule
 (enforce
  (dune
   (instrumentation
    (and
     (present bisect_ppx)
     (absent landmarks))))))

With this configuration, (instrumentation (backend landmarks) (deps foo.txt)) is replaced with (instrumentation (backend bisect_ppx)). Note that the other arguments of the removed field (such as deps) are specific to its backend, and are not carried over to the new field.

library

(dune (library _)) is a selector for the dune top level stanza named library:

Stanza:

(library <FRAGMENT>)

name

(dune (library (name _))) is a selector for the name field of an library stanza:

Stanza:

(library
 (name <FRAGMENT>))

It is almost identical to the name construct of the executable selector, thus we are not documenting it in details here.

public_name

(dune (library (public_name _))) is a selector for the public_name field of an library stanza:

Stanza:

(library
 (public_name <FRAGMENT>))

It is almost identical to the public_name construct of the executable selector, thus we are not documenting it in details here.

has_field

(dune (library (has_field FIELD))) is a predicate for checking the presence of fields in a library stanza.

All fields from the generic has_field selector that are marked as applicable to library stanzas are available here. See the has_field section for details on common fields like instrumentation, lint, name, preprocess, and public_name.

Additionally, the following library-specific fields are supported:

inline_tests

(dune (library (has_field inline_tests))) checks for the presence of the inline_tests field.

Stanza:

(library
 (inline_tests))

When enforced, dunolint suggests adding (inline_tests) to the library if not present.

Negation: When the negation is enforced, dunolint suggests removing the field entirely, including any arguments it may have (e.g., (inline_tests (deps ./test_data))).

Examples:

Stanza:

(library
 (name my_test)
 (inline_tests))

Condition: (dune (library PREDICATE))

PredicateResult
(has_field inline_tests)True
(not (has_field inline_tests))False. Suggestion: remove the field

package

(dune (library (has_field package))) checks for the presence of the package field.

Stanza:

(library
 (package my_package))

When enforced, dunolint does not suggest adding a field (there is no valid placeholder value). To auto-add a package field, use the (package (equals PKG)) predicate instead.

Negation: When the negation is enforced, dunolint suggests removing the field entirely.

Examples:

Stanza:

(library
 (name my_lib)
 (package my_package))

Condition: (dune (library PREDICATE))

PredicateResult
(has_field package)True
(not (has_field package))False. Suggestion: remove the field

package

(dune (library (package _))) is a selector for the package field found in library stanzas.

Stanza:

(library
 (package <PACKAGE_NAME>))

Its predicates are:

  1. (equals PACKAGE_NAME)

Returns true iff the library's package name is exactly PACKAGE_NAME.

When enforced, dunolint suggests setting the package name to the supplied value. If the field is not present, it will be inserted. Doesn't support suggestion when negated.

  1. (is_prefix STRING) and (is_suffix STRING)

Returns true iff the library's package name starts (resp. ends) with the specified string.

When enforced with is_prefix, dunolint suggests prepending the prefix if not already present. When enforced with is_suffix, dunolint suggests appending the suffix if not already present.

Negation: When the negation is enforced, dunolint suggests removing the prefix (resp. suffix) from the package name if present.

Absent field: Only (equals PACKAGE_NAME) can initialize an absent package field. Predicates like (is_prefix _) and (is_suffix _) cannot provide an initial value, so enforcing them on an absent field will fail. Use (equals PACKAGE_NAME) to initialize the field, or add it manually before running dunolint.

Examples:

Stanza with package present:

(library
 (name my_lib)
 (package pkg))

Condition: (dune (library PREDICATE))

PredicateResult
(package (equals pkg))True
(package (is_prefix pk))True
(package (is_suffix kg))True
(package (is_prefix prefix_))False. Suggestion: (package prefix_pkg)
(package (is_suffix _suffix))False. Suggestion: (package pkg_suffix)
(not (package (is_prefix pk)))False. Suggestion: (package g)

Stanza with package absent:

(library
 (name my_lib))

Condition: (enforce (dune (library PREDICATE)))

PredicateResult
(package (equals pkg))Field inserted: (package pkg)
(package (is_prefix prefix_))Enforcement failure
(package (is_suffix _suffix))Enforcement failure

libraries

(dune (library (libraries _))) is a selector for the libraries field of a library stanza:

Stanza:

(library
 (libraries <FRAGMENT>))

The predicates of the libraries selector are:

  1. (present LIBRARY_NAMES)

Returns true iff all the library names specified are present in the list of dependencies found in the fragment.

When enforced, dunolint suggests adding the library name(s) not already present. New libraries are added to the last section (when sections are delimited by comments) and sorted alphabetically within that section.

  1. (absent LIBRARY_NAMES)

Returns true iff none of the library names specified are present in the list of dependencies found in the fragment.

When enforced, dunolint suggests removing the library name(s) present.

Negation of present and absent:

  • With a single library name, (not (present a)) is equivalent to (absent a), and (not (absent a)) to (present a). They are enforced as such.
  • With several library names, (not (present a b)) holds when at least one of them is absent, and (not (absent a b)) holds when at least one of them is present. This doesn't determine which libraries to remove or add, thus such negations are only checked, and an enforcement failure is reported when they don't hold.

Prefer absent over a negated present, and present over a negated absent: (absent a b) requires that none of the libraries is present, and (present a b) that all of them are, which dunolint can always enforce.

Empty arguments: present and absent expect at least one library name.

Examples:

Stanza:

(library
 (name mylib)
 (libraries base core))

Condition: (dune (library (libraries PREDICATE)))

PredicateResult
(present base core)True
(present base)True
(present async)False. Suggestion: add async, keep existing values
(absent async)True
(absent core async)False. Suggestion: remove core
(absent base core)False. Suggestion: remove base and core
(not (present core))False. Suggestion: remove core
(not (present core async))True
(not (present base core))Enforcement failure: removing either base or core would do
(not (absent core))True
(not (absent async))False. Suggestion: add async, keep existing values
(not (absent core async))True
(not (absent async lwt))Enforcement failure: adding either async or lwt would do

Note: the constructor mem is deprecated. It keeps its behavior, in particular enforcing (not (mem a b)) removes all the supplied libraries. Replace (mem ...) by (present ...), and (not (mem ...)) by (absent ...).

modes

(dune (library (modes _))) is a selector for the modes field of a library stanzas:

Stanza:

(library
 (modes <FRAGMENT>))

The compilation modes supported are

MODE := byte | native | best | melange

Compilation modes are ordered by dunolint as: byte < native < best < melange.

The predicates of the modes selector are:

  1. (present MODES)

Returns true iff the modes specified are all present in the list of values found in the fragment.

When enforced, dunolint suggests adding the mode(s) not already present in the list of values found in the fragment.

  1. (absent MODES)

Returns true iff none of the modes specified are present in the list of values found in the fragment.

When enforced, dunolint suggests removing the mode(s) present.

Negation of present and absent: They follow the same rules as for the libraries selector above: with a single mode, they are enforced as their opposite, and with several modes, they are only checked.

Prefer absent over a negated present, and present over a negated absent: (absent a b) requires that none of the modes is present, and (present a b) that all of them are, which dunolint can always enforce.

Empty arguments: present and absent expect at least one mode.

When the stanza has no modes field, enforcing a condition that doesn't add any mode, such as (absent byte), doesn't create the field, since an empty (modes) field isn't equivalent to an absent one.

Examples:

Stanza:

(library
 (modes byte native))

Condition: (dune (library (modes PREDICATE)))

PredicateResult
(present byte native)True
(present byte)True
(present best)False. Suggestion: add best, keep existing values
(absent best)True
(absent native best)False. Suggestion: remove native
(not (present native))False. Suggestion: remove native
(not (absent best))False. Suggestion: add best, keep existing values

Note: the constructors mem, has_mode and has_modes are deprecated. They keep their behavior, in particular enforcing (not (mem a b)) removes all the supplied modes. Replace (mem MODES), (has_mode MODE) and (has_modes (MODES)) by (present MODES), and their negation by (absent MODES).

Fields shared with other stanzas

This stanza shares some sub selectors with other stanzas. See: instrumentation, libraries, lint, preprocess.

For example, you can use the (dune (library (instrumentation _))) syntax if you want the instrumentation selector to apply to the library stanza only.

libraries

(dune (libraries _)) is a selector for the libraries field found in stanzas library and executable.

Stanza:

(library
 (libraries <FRAGMENT>))

Its predicates are documented in the library (libraries) section above.

lint

(dune (lint _)) is a selector for the lint field found in stanzas library, executable and executables.

Stanza:

(library
 (lint <FRAGMENT>))

Currently, it is required to be followed by a pps selector.

pps

(dune ({lint|preprocess} (pps _))) is a selector for the pps field found in stanzas library, executable and executables

Stanza:

(library
 (preprocess (pps <FRAGMENT>)))

Its predicates are:

  1. (present PP_NAMES)

Returns true iff all the pps specified are present in the FRAGMENT.

When enforced, dunolint suggests adding the pp(s) not already present to the list of arguments, initiating the preprocess and pps field if needed.

  1. (absent PP_NAMES)

Returns true iff none of the pps specified are present in the FRAGMENT.

When enforced, dunolint suggests removing the pp(s) present from the list, along with any flags that may be associated with them.

Negation of present and absent: They follow the same rules as for the libraries selector: with a single pp, they are enforced as their opposite, and with several pps, they are only checked.

Prefer absent over a negated present, and present over a negated absent: (absent a b) requires that none of the pps is present, and (present a b) that all of them are, which dunolint can always enforce.

  1. (pp PP_NAME)

This is equivalent to (present PP_NAME).

  1. (flag (name FLAG) (param PARAM) (applies_to APPLIES_TO))
  • PARAM: any | none | some | (equals VALUE)
  • APPLIES_TO: any | driver | (pp PP_NAME)

Returns true if one of the flags present fits the specification.

Flags usually start with one or two '-' characters. We recommend dunolint users to adhere strictly the the -flag=PARAM syntax for configuring ppx flags accepting parameters, as this ensure the entire construct is parsed as a single atom.

Applies_to:

  • Flags may be applied to the ppx driver itself. This is the case for generic flags implemented by ppxlib for example. In this case, the dunolint convention is to place them first, right after the pps field, and before any ppx. This is the case for the flag unused-code-warnings flag below:
 (preprocess
  (pps
   -unused-code-warnings=force
   ppx_compare
   ppx_enumerate
   ...
  ))
  • Or flags may be custom constructs targeting a dedicated ppx. In this case, the dunolint recommendation is to place them right after the ppx they are targeting. This is the case for the check-doc-comments flag below
(lint
  (pps ppx_js_style -check-doc-comments))

Evaluation:

Param matching evaluation is pretty self explanatory, any matches anything, none means no param, some means a param with any value, and equals expects an exact match for the param value.

It is possible to enforce the (flag _) predicate, when the specification is unambiguous as to how to create a new flag if a matching one is not already present (for example, it can't have PARAM=any, etc.). The (flag _) predicate may only be negated when PARAM=any in which case dunolint will suggests removing any matching flag.

  1. (pp_with_flag (pp PP_NAME) (flag FLAG) (param PARAM))

This is a convenient wrapper for combining the two previous predicates into a single one, to assert the present of a pp with a flag applied to it.

Examples:

Stanza:

(library
 (name dunolint-lib)
 (lint
  (pps ppx_js_style -check-doc-comments)))

Condition: (dune (lint (pps PREDICATE)))

PredicateResult
(present ppx_js_style)True
(present ppx_js_style ppx_expect)False. Suggestion: add "ppx_expect"
(absent ppx_expect)True
(absent ppx_js_style ppx_expect)False. Suggestion: remove "ppx_js_style -check-doc-comments"
(not (present ppx_js_style))False. Suggestion: remove "ppx_js_style -check-doc-comments"
(not (present ppx_js_style ppx_expect))True
(flag (name -allow-let-operators)(param none)(applies_to (pp ppx_js_style)))False. Suggestion: add "-allow-let-operators" right after "ppx_js_style"

preprocess

(dune (preprocess _)) is a selector for the preprocess field found in stanzas library, executable and executables.

Stanza:

(library
 (preprocess <FRAGMENT>))

It supports two forms:

  1. no_preprocessing

This is a predicate that returns true iif the FRAGMENT is set to exactly this value.

When enforced, dunolint suggests to replace any existing setting with that field. Doesn't support suggestion when negated.

  1. (pps _)

In this case, the construction follows with a pps selector.

When enforced, dunolint suggests to either replace or initiate a pps field based on the enforcement of that selector. Doesn't support suggestion when negated.

stanza

(stanza KIND) defines a predicate that evaluates to true iif the fragment is located within a stanza of the specified kind.

Supported kinds: include_subdirs, library, executable, executables.

To restrict an invariant to stanzas of a given kind, prefer the selector of that kind, such as (dune (library (has_field preprocess))). Avoid using the stanza predicate in enforced invariants: dunolint won't change the kind of a stanza, so such invariants would never be fixed automatically. They would only be checked, and a violation would be reported for every stanza of a different kind.