baler.toml

A {box}-{baler} module needs one metadata file: baler.toml. It sits at the root of the module project, one level above the source directory, and declares identity, dependencies, and (optionally) compiled code. It can contain up to five tables:

Table Required Purpose
[project] yes Module identity and source location
[dependencies] no Runtime R package and baler-module dependencies
[extras] no Dev-only dependencies (test frameworks, mocks) and external tools and Python packages (doc generators, linters, Python libraries)
[compiled-code] no Location(s) of compiled code, build-time-only package deps, and what baler bundle ships
[tool.test] no Test framework configuration (currently advisory only)

[project]

This block defines the authorship of the module, and this block is always required. As what the table below said, this contains the info of the author of {box}-{baler} module library:

Field Type Required Notes
name string yes Module name. Also the default source-directory name when src is omitted.
version string yes Semver (MAJOR.MINOR.PATCH), optionally with a pre-release tag. Use one to mark a development version, for example 0.3.0-dev or 0.3.0-dev.1, which sorts before 0.3.0. A requirement is checked as if the tag were absent, so 0.3.0-dev satisfies >=0.3.0. Parsed when the file loads; a malformed version fails there, not later inside whoever depends on this module. Dependency ranges (^, >=, etc.) in another module’s [dependencies.baler] entry resolve against it.
description string yes May be empty.
authors array yes See Authors below.
license string yes SPDX identifier or free-form text.
r_version string yes Minimum R version (e.g. ">=4.0.0"). See note below.
readme string no Path to a README file.
repository string no
keywords array of strings no Defaults to empty.
src string no Path to the source directory, relative to the project root. Defaults to a directory named exactly name. The chosen directory must contain __init__.r or __init__.R.
NoteTechnical Notes
  • r_version is enforced against the R installation on PATH, at both baler install and baler lock time. A mismatch fails loudly rather than letting a module load under an R it wasn’t declared compatible with.

  • Both __init__.r and __init__.R are accepted (case-insensitive entry-point detection). This matches {box} module’s internal behaviour.

Authors

Each entry is either a plain string or an inline table:

authors = [
    "Jane Doe",
    { name = "Joshua Marie", email = "joshua.marie.k@gmail.com", url = "https://joshuamarie.com", orcid = "0000-0000-0000-0000" },
]

Inline-table fields:

Field Required Notes
name yes Display name
email no
url no Personal or organisational homepage
orcid no ORCID iD

[dependencies]

Runtime dependencies, both R packages and other baler modules, live in the same top-level table (a sibling of [project], not nested under it):

  • A bare key is an R package, resolved off CRAN (or a custom repo). This is the equivalent of Imports in an R package’s DESCRIPTION.
  • A nested [dependencies.baler] table holds other baler modules this module depends on.
[dependencies]
dplyr = "*"
ggplot2 = ">=3.4.0"
fable = { version = "*", repo = "https://tidyverts.r-universe.dev/" }

[dependencies.baler]
other_module = { git = "https://github.com/<user>/<repo>", tag = "v1.2.0" }
utils = { git = "https://github.com/<user>/<repo>", branch = "main", module_dir = "utils" }

A bare string value works the same as a table with only version set: the repo used is the default mirror, https://cloud.r-project.org.

NoteTechnical Notes

Because module deps and package deps share one table, told apart by whether an entry sits under the reserved baler key rather than by any type/mode field, an R package literally named baler cannot be declared as a bare key here.

Version-spec syntax

Version requirements follow Cargo.toml-style syntax, always written as strings:

Syntax Meaning
"*" Any version
">=1.0.0" Lower bound only
">=1.0.0, <2.0.0" Inclusive range (comma-separated bounds)
"^1.2.0" SemVer-compatible with 1.2.0
"=1.2.3" Exact pin

CRAN version strings that use dashes (e.g. 4.0-3) are normalised to dots before parsing.

Module dependencies: git and url

A [dependencies.baler] entry names where the module comes from, using the same keys baler install takes as flags:

Field Required Notes
version no Constraint string, e.g. "^1.0". Defaults to "*" when omitted. Still checked against the fetched module’s own declared version.
git one of git / url A https://github.com/... repository URL. Only github.com URLs are supported, the same as baler install --git.
branch, tag, rev no At most one of these, and only together with git. Selects a specific ref to fetch.
module_dir no Directory inside the git repo that holds the module, for a repo holding more than one. Only valid with git.
url one of git / url A direct link to an already-bundled .tar.gz, e.g. a GitHub Release asset produced by baler bundle.
[dependencies.baler]
helpers = { git = "https://github.com/user/repo", tag = "v1.2.0", module_dir = "helpers" }
tools = { url = "https://github.com/user/repo/releases/download/v1.0.0/tools_1.0.0.tar.gz" }
core = "^2.0"    # bare string: registry lookup, not implemented yet

Exactly one of git or url may be set. Setting both, or setting branch/tag/rev/module_dir without git, fails to load baler.toml immediately, before any resolution or fetch is attempted. Unknown keys (a typo such as tga) are rejected the same way, rather than being silently ignored.

A bare-string entry, or a table with only version set, parses fine but has no source to resolve against, since there’s no default module registry yet:

[dependencies.baler]
other_module = "*"   # will fail at resolution time, no registry yet

Extra dependencies for development

[extras], by default, has the same shape as [dependencies], bare keys for R packages, a nested .baler table for baler modules, except dependencies under this block are only needed during development (a test framework, a mock module), not at runtime. It also has one thing [dependencies] doesn’t: [extras.external], for non-R tools and Python packages. Here’s an example:

[extras]
testthat = "*"
covr = "*"
# rlang = "*"

[extras.baler]
# mock_module = { url = "https://github.com/user/repo/archive/refs/tags/vX.Y.Z.tar.gz" }
# utils = { git = "https://github.com/<user>/<utils>" }

[extras] packages are recorded in baler.lock under the extras group. A normal baler install does not install them.

External development tools

Under [extras.external] block, you can declare anything a module’s development needs that isn’t a traditional R package or a box-bale module: a command-line tool such as Quarto, or a Python package. {baler} never fetches or installs any of it, it only checks for it and reports. Nothing under this table is written to baler.lock, since what is installed on a machine isn’t something baler.toml can pin. Each entry picks its kind with type (two, as of now):

  1. cli: A command binary name on PATH. The declared tools under this type are checked through running <tool> --version.

  2. python: A Python distribution name (strictly on this sense, so for the scikit-learn library, it should be written as scikit-learn, not sklearn). The declared packages are checked by reading the package’s installed version from one Python interpreter.

Here’s an example:

[extras.external]
pandoc = ">=3.0"            # a bare string is a `cli` entry

[extras.external.quarto]
type = "cli"                # the default when `type` is left out
version = ">=1.4"
hint = "https://quarto.org/docs/get-started/"

[extras.external.numpy]
type = "python"
version = ">=1.24,<3"

[extras.external.internal-stats]
type = "python"
version = ">=2.0"
index = "https://pypi.internal.example/simple"

cli entries

Field Required Notes
version yes A requirement in the same syntax as Version-spec syntax above.
hint no Printed when the tool is missing, e.g. a download page.

The key must be a bare command name. A path separator is rejected when baler.toml loads. The version is read from the first dotted number in the tool’s --version output (3.1 is read as 3.1.0). A tool that prints its version some other way is reported as unreadable, not as a mismatch. Messages include the path of the tool that was checked.

python entries

Field Required Notes
version yes A PEP 440 specifier such as >=1.24,<2 or ~=1.24. This is not the Cargo-style syntax used elsewhere in this file, but it is the same string pip accepts.
python no Interpreter to check, instead of the automatically resolved one. A bare name is looked up on PATH, anything with a path separator is used as given.
index no A private package index URL. Only changes the install hint.
git no A git URL. Only changes the install hint. Cannot be combined with index.

All python entries are checked in one interpreter run, so which interpreter is used matters. It is the first of:

  1. The entry’s own python field.
  2. RETICULATE_PYTHON, since that is the interpreter R’s {reticulate} will use.
  3. An active VIRTUAL_ENV or CONDA_PREFIX.
  4. python3, then python, on PATH.

An explicit setting (1 or 2) that doesn’t resolve is reported as an error instead of quietly falling through to a different interpreter. Versions are read from package metadata, so checking a package never imports it.

Every message names the interpreter that was checked, and a missing or outdated package comes with an install hint that targets that same interpreter, for example /home/me/.venv/bin/python -m pip install "numpy>=1.24". index and git only change the wording of that hint, since detection doesn’t depend on where a package came from.

Checking

baler compile and a --path install (installing straight from a project directory) check the root project’s entries and print a note for anything missing or out of range. This is informational only: it never fails the command. A --git, --url, or registry install skips this check entirely, since those fetch something closer to a finished release than a project someone is actively developing. A fetched module’s own [extras.external] is never checked, because checking runs external programs.

Entries are validated when baler.toml loads. These fail immediately:

  • An unknown type, or an unknown key in an entry (a typo such as hnit).
  • python, index, or git on a cli entry, or hint on a python entry.
  • index and git on the same entry.
  • A cli key that is not a bare command name, or a python key that is not a valid distribution name.

Configuration for handling native compiled codes

The [compiled-code] block is not always required, until there exist codes written in native languages which requires compilation. In this block, you declare where a module’s compiled code lives, any build-time-only R packages whose headers Makevars / R CMD SHLIB needs (most commonly Rcpp or cpp11), and what baler bundle ships.

[compiled-code]
path = "cpp/"                 # single directory, relative to the module's source dir
# path = ["cpp/", "extra/"]   # or an array, for multiple directories

build_deps = { Rcpp = "*" }   # installed before compilation; not a runtime dependency
binary = true                 # `baler bundle` compiles and ships the binary
keep_source = false           # only with binary = true: also ship native source
Field Type Required Notes
path string or array of strings no Directory, or directories, containing native sources + Makevars. Relative to the module’s own source directory (the same base src uses), not the project root baler.toml lives in. A single location can be written as a plain string; more than one as an array.
build_deps table no Map of package name -> version requirement (same syntax as [dependencies]). These are resolved and installed before R CMD SHLIB runs. They do not become runtime dependencies; list them again under [dependencies] if the compiled code also needs them loaded at runtime. They are recorded in baler.lock under the build group.
binary boolean no Defaults to false. When true, baler bundle compiles the native code in place first and ships the compiled .lib/ in the archive, without the native source. See Bundle settings.
keep_source boolean no Defaults to false. Only valid together with binary = true. Also ships the native source next to the compiled binary.

Bundle settings

The binary and keep_source options describe what the module ships, so they live with the module instead of being retyped on every run. Note: Its predecessor, {carrier}, it only flags through --binary and --keep-source, but the flags no longer exist, and passing one is now a command-line error. The following table summarizes what each combination of those options produces:

binary keep_source The result of the archive
false (default) false (default) R source and native code source, no compiled binary. Compiled code is built on install.
true false Compiled .lib/ binary, native source left out.
true true Compiled .lib/ binary and native source.
false true Rejected when baler.toml loads: keep_source needs binary = true.

If binary = true but the module has no native code, baler bundle prints a warning and bundles without a binary. A binary bundle only works reliably on the platform and R version it was built for, see Binary bundles.

Resolution rules

  1. If path is set, those directories (relative to the module’s source dir) are used, whether written as a single string or an array.
  2. Otherwise, {baler} scans the whole module source tree for directories that contain a Makevars (or recognised C/C++ sources). This is the behaviour you get when [compiled-code] is omitted entirely.

The directory name itself is free (c/, cpp/, fortran/, src/, …); detection is driven by the presence of build artefacts, not by a fixed folder name. See also the scaffolding produced by baler init --native.

NoteTechnical Notes
  • FORTRAN scaffolding is recognised by the language parser but not yet supported by the build pipeline (R CMD SHLIB is only invoked for .c/.cpp/.cc/.cxx sources).
  • Compiled shared objects are placed in <module>/.lib/ (next to __init__.R) so that box::file() resolves them correctly.
  • Build results are cached by source hash + R platform + R version.

Unit Testing

Under [tool.test] block, advisory configuration for a future test runner. As of now, {baler} does not yet execute tests.

Field Type Required Notes
framework string yes e.g. "testthat"
dir string yes Directory containing tests (auto-detected when "__tests__") is used

Full template

This is the exact text written by baler init. [compiled-code] is only included at all when --native is passed; a plain baler init leaves it out entirely rather than writing a commented-out placeholder for it. baler init --src <dir> (see Initialization and Scaffolding) writes the # src = ... line as a real src = "<dir>" line when <dir> isn’t named after the module. Everything else is identical.

[project]
name = "convert"
version = "0.1.0"
description = ""
authors = [
    { name = "Your Name", email = "you@example.com" },
]
license = "Unknown"
r_version = ">=4.0.0"
repository = ""
keywords = []
# readme = "README.md"
# src = "convert"    # path to the source directory containing __init__.R
                    # defaults to a directory named after the module

[dependencies]
# dplyr = "*"
# ggplot2 = ">=3.4.0"
# fable = { version = "*", repo = "https://tidyverts.r-universe.dev/" }

[dependencies.baler]
# A module needs a source: `git` (a github.com repo) or `url` (a
# bundled .tar.gz). `git` can take one of branch/tag/rev, and
# module_dir for a repo holding more than one module. `version` is
# optional.
# other_module = { git = "https://github.com/user/repo", tag = "v1.0.0" }

# [extras]/[extras.baler] hold deps only needed outside normal
# runtime use (tests, mocks, dev tooling), same shape as
# [dependencies]/[dependencies.baler] above. [extras.external] adds
# one more kind found only here: external tools (command-line
# programs, Python packages), never loaded by box, so never part of
# [dependencies] at all.
[extras]
# testthat = "*"

[extras.baler]
# mock_module = { git = "https://github.com/user/repo" }

[extras.external]
# baler doesn't install these, only checks for them. A bare string is
# a command-line tool with that version requirement. A table picks a
# type: "cli" (the default, with an optional `hint`) or "python" (a
# PyPI package, checked in one Python interpreter).
# quarto = ">=1.4"
# numpy = { type = "python", version = ">=1.24" }

# [tool.test]
# framework = "testthat"
# dir = "tests"

When baler init --native cpp (or --native c, etc.) is used, a [compiled-code] block is added, populated with the appropriate path and build_deps for the chosen language / backend:

[compiled-code]
# Native code is auto-detected under this module's source dir, no
# path needed for the default src/ layout. Only set path if compiled
# code lives somewhere else, or in more than one place.
# path = "native/"
# path can also be an array: path = ["native/", "extra/src"]
build_deps = { Rcpp = "*" }
# build_deps is resolved and installed before compiling.
# Does not imply a runtime dependency; list it in
# [dependencies] too if the compiled code also needs it
# loaded at runtime