Mold

by xtfc

A fresh approach to managing common development tasks like running tests, checking code quality, or building documentation.

Mold is heavily built around a few core ideas:

By reusing parameterized modules, Mold enables new projects to rapidly get off the ground with a set of standard tasks. For example, python.mold provides tasks for general Python project maintenance: autoformatting with black, linting with flake8, documentation generation with Sphinx, etc.

These modules can also provide consistency across projects from different teams, platforms, and toolchains: linting can generally be run with the lint recipe, tests can generally be run with the test recipe, etc. Consistency cuts down on time spent onboarding and adjusting after switching projects

When consistency between projects can't be achieved completely, Mold makes your tasks discoverable and accessible without having to dig up the documentation. Simply running mold will list all of the magic spells in your project's grimoire.

Installation

Check the GitHub releases page for binaries, or get the ready-to-use shell script here:

curl -sSfL xtfc.org/mold.sh | sh

mold.sh is a stand-in for the Mold binary. It's intended to be tracked by version control, so it's suitable for use in environments that don't already have Mold installed, like CI/CD or your coworker's computer. It does require the host environment to have internet access and wget or curl installed, as it transparently downloads a full binary to handle actual execution.

Usage

Mold is so fun and easy to use. Just type mold (or maybe ./mold.sh, if you're doing it that way). It should present you with a list of tasks and helpful descriptions. If you append a task name to the command, like mold foo, it will execute the foo task for you. You can run many tasks in sequence, like mold foo bar.

There's also some other junk sitting around if you run mold --help.

Reference

Mold reads a recipe file that describes the available incantations and all sorts of fun parameterization options. Recipe files are written in YAML. Maybe some day it will support XCL.

A recipe file describes tasks that can be executed, script runtimes, environment variable definitions, and conditional environment configurations. It may also define a required minimum version of Mold.

Recipes

A recipe describes a task that can be executed and can take any of several forms:

A command is an executable and a list of arguments. It does not support any sort of variable substitution or interpolation; the list of arguments is used as a literal and passed straight to the OS for execution.

A script is an inline script file and a script runtime. The inline script will be written to a temporary file and invoked using the script runtime. This is suitable for small scripts on the order of ~10 lines.

A file is a separate script file, located in the mold/ directory by default, and a script runtime. The file will be invoked using the script runtime. This is suitable for scripts of any size or language. The script file directory can be changed in the root of the recipe file.

A module is a reference to a remote Git repository that contains a separate Mold configuration. Modules can target a specific branch / tag as well as a specific recipe file inside the repo. Recipes inside the module can be accessed with a prefix.

All recipes can be configured with an optional help string, prerequisite tasks, environment variable definitions, and conditional environment variable definitions.

Runtimes

A runtime defines an interpreter that can be used to execute scripts and files. Runtimes are defined by a name, command, and file extensions. The name is used as an identifier by scripts and files, so it must be unique. The command is a list of arguments that can be passed to the OS to execute the interpreter; any "?" elements will be replaced with the file to be executed. The file extensions are defined as a list of potential extensions to attach to a filename.

See std.mold for example runtime definitions.

Includes

An include is a reference to a module that is included at the top-level, rather than behind a namespace prefix. This is useful for things like std.mold that define general-purpose runtimes or recipes.

Variables

Environment variables are used to parameterize recipes. When using modules, it's often beneficial to use variables to ensure the module is generalized sufficiently. Variables can be configured at the root level or at an individual recipe level.

Mold automatically exports several environment variables that describe its runtime context:

In simple configurations, several of the directories will be identical, but they can be useful in more complicated configurations involving nested modules.

Conditional Environments

Conditional environments allow environment variables to be defined based on a runtime setting. Environments are defined as a test expression and a variable map. The test is a simple expression that checks for the presence or absence of values in the active Mold environment list. If a test evaluates to true, the corresponding variable map will be applied. Conditional environments are applied in-order, overwriting values defined in the non-conditional variables map and in previous conditional environments.

Active environments are set as a comma-separated list of alphanumeric strings in the $MOLDENV environment variable or the --env / -e CLI flag. Additional environments can be appended one at a time using the --add / -a CLI flag.

Test expressions are written in a minimal language designed for checking combinations of active environments. Any environment identifier evaluates to true if that identifier is active. Boolean and is expressed with the + operator, while boolean or and not are expressed with the | and ~ operators, respectively. Subexpressions can be grouped using ().

Environment identifiers can also have underscores and hyphens. Whatever.