Task Runner
mino task <name> executes named tasks defined in mino.edn. Tasks are ordinary mino functions referenced by qualified symbols. Dependencies between tasks are resolved automatically.
Quick start
Add a :tasks key to your mino.edn:
{:paths ["src" "lib"]
:tasks
{hello {:doc "Say hello"
:task myproject.tasks/hello}
greet {:doc "Greet then say hello"
:deps [hello]
:task myproject.tasks/greet}}}Define the task functions in a regular mino source file:
(ns myproject.tasks)
(defn hello []
(println "Hello!"))
(defn greet []
(println "Welcome to the project."))Run a task:
mino task greet
# --- hello ---
# Hello!
# --- hello (0.03ms) ---
# --- greet ---
# Welcome to the project.
# --- greet (0.02ms) ---List available tasks:
mino task
# Available tasks:
# hello Say hello
# greet Greet then say helloTask definitions
Each entry in the :tasks map is a symbol key mapped to a spec:
task-name {:doc "Description shown by 'mino task'"
:deps [dep-a dep-b]
:task some.namespace/function-name}:task(required)— a qualified symbol naming a zero-argument function. The namespace is loaded automatically viarequire.:doc(optional)— a short description displayed when listing tasks.:deps(optional)— a vector of task names that must run before this task. Dependencies are resolved in topological order; each task runs at most once even in a diamond dependency graph.
Dependency resolution
The task runner performs a depth-first topological sort. It detects and reports:
- Circular dependencies
- References to undefined tasks
- Missing
:taskkeys
In a diamond dependency (D depends on B and C, both depend on A), task A runs exactly once.
Self-hosting build
mino itself uses the task runner for its own build. The mino.edn in the mino repository defines tasks for compilation, testing, and release:
{:paths ["src" "lib"]
:tasks
{gen-core-header {:doc "Escape src/core.clj into src/core_mino.h"
:task mino.tasks.builtin/gen-core-header}
build {:doc "Compile and link the mino binary"
:deps [gen-core-header]
:task mino.tasks.builtin/build}
clean {:doc "Remove build artifacts"
:task mino.tasks.builtin/clean}
test {:doc "Build and run the test suite"
:deps [build]
:task mino.tasks.builtin/test-suite}}}The build task implements incremental compilation with header dependency tracking (via -MMD compiler flags), matching the behavior of a traditional Makefile but written entirely in mino.
Bootstrap
Since mino task build needs a mino binary to run, the first build comes from a small bootstrap Makefile at the repository root:
makeThe Makefile generates the bundled-source headers (core.clj plus the clojure.* and mino.* namespaces baked into the binary) and compiles every subsystem in one cc invocation. It does nothing else — every other build, test, and tooling task lives in the task runner. After bootstrap, ./mino task build takes over with incremental compilation: editing a header recompiles only the translation units that include it.
Writing task functions
Task functions are ordinary mino functions. They can use any primitive or library available to the mino runtime. Some primitives commonly used in tasks:
sh!— run a shell command, throw on non-zero exitfile-mtime— file modification time in milliseconds (for incremental builds)file-exists?— check whether a file or directory existsspit/slurp— write and read filesstr-replace— single-pass string replacementgetenv— read environment variables
Tasks print a timing banner automatically. No special macros or DSL required.
Error handling
If a task function throws an exception, execution stops immediately. Tasks that already completed are not rolled back (there is no transaction model). The exception propagates to the caller with a full stack trace.
The mino task CLI validates task names before evaluation. Only alphanumeric characters, hyphens, and underscores are accepted.