Skip to main content

Spec Reference

Terse companion to Anatomy of a Spec.

Globals​

GlobalAllowed formsWhen omitted
IDENTITY"namespace.name@revision"error, always required
FETCHstring, table, array of tables, or functionerror, unless USER_MANAGED
STAGEtable or functionevery fetched archive is extracted
BUILDstring or functionnothing runs
INSTALLstring or functionthe staged tree becomes the package
SETUPtable of named pairsno host-side work
OPTIONSschema table or functionoptions are accepted unvalidated
PRODUCTStable or functionthe package exports nothing
DEPENDENCIESarray of entry tablesnone
PLATFORMSarray of strings, darwin, linux, windows, optionally -arm64 or -x86_64every platform
DISPLAYstring or function of the optionsthe package's rows say nothing beyond their identity. Requires envy 0.4.2
USER_MANAGEDboolean or functionfalse, meaning cache-managed
EXPORTABLEbooleanfalse, so only fetched bytes are kept for export
VENDORarray of selector stringsthe whole install directory is copied when a manifest vendors this package. Requires envy 0.4.0

DEFAULT_SHELL is a manifest global, not a spec global. A spec picks a shell per call with envy.run(..., { shell = ... }).

Verb signatures​

FETCH(tmp_dir, options)
STAGE(fetch_dir, stage_dir, tmp_dir, options)
BUILD(install_dir, stage_dir, fetch_dir, tmp_dir, options)
INSTALL(install_dir, stage_dir, fetch_dir, tmp_dir, options)
SETUP.<name>.CHECK(pkg_dir, options) -- pkg_dir is nil for user-managed
SETUP.<name>.INSTALL(pkg_dir, options)
PRODUCTS(options)
OPTIONS(options)
DISPLAY(options)
USER_MANAGED()

Every directory argument arrives with a trailing path separator. The one exception is the tmp_dir handed to a source.fetch function, which does not.

options is the validated table everywhere except PRODUCTS, which is resolved before OPTIONS runs and receives the manifest entry's options as written. Guard every read in a PRODUCTS function — details.

Return values:

VerbReturns
FETCHnothing, or a URL string, optionally followed by a sha256
STAGE, BUILD, INSTALLnothing
SETUP.<name>.CHECKtrue for satisfied, false for not, or a script string to run
SETUP.<name>.INSTALLnothing, or a script string to run
PRODUCTSthe products table
OPTIONSnothing, true, false, or an error message string
DISPLAYone line of printable text, or nil for none
USER_MANAGEDboolean

FETCH table fields​

FieldMeaning
sourceURL, local path, or git URL. Required.
sha256Verifies the download, and lets the cache reuse it.
refCommit for a git source.
destRename the downloaded file. A plain filename, no separators.
post_dataHTTP POST body. HTTP and HTTPS only.

An array of these tables fetches several files.

STAGE table fields​

FieldMeaning
stripDrop this many leading path components.
onlyExtract just these archive-relative paths or globs, matched after strip. A leading ! excludes.

VENDOR selectors​

Which parts of the install directory a manifest copies into the project tree when it vendors this package. Absent or empty selects everything.

VENDOR = { "include/**", "LICENSE", "!include/internal/**" }

Paths are relative to the install directory, and the glob rules are the ones STAGE's only uses. A leading ! excludes, and an exclusion beats an inclusion. The list is validated when the spec loads, so a malformed pattern fails before anything is fetched.

The spec says what is worth copying. Whether a copy happens at all is the manifest's vendor field.

Platform-aware specs​

A spec runs on the machine doing the install, so the platform is read at spec load time rather than declared:

PLATFORMS = { "darwin", "linux", "windows" }

PRODUCTS = { mytool = "bin/mytool" .. envy.EXE_EXT }

FETCH = function(tmp_dir, opts)
local ext = (envy.PLATFORM == "windows") and ".zip" or ".tar.gz"
return {
source = base .. opts.version .. "-" .. envy.PLATFORM_ARCH .. ext,
sha256 = hashes[opts.version][envy.PLATFORM_ARCH],
}
end

envy.EXE_EXT is ".exe" on Windows and "" elsewhere, so one PRODUCTS line covers all three. A product that only exists on some platforms takes its own platforms field instead. String verbs run under PowerShell on Windows, so a spec with a BUILD string needs a portability plan.

PRODUCTS entry fields​

A string value is shorthand for { value = "...", script = true }.

FieldDefaultMeaning
valuerequiredPath relative to the package directory, or an arbitrary string.
scripttrueWhether to deploy a wrapper script into the project's bin directory.
platformsallRestrict this product to some platforms.

DEPENDENCIES entry fields​

FieldNotes
specIdentity. Required, except for a product-only reference.
sourceURL, path, git URL, or { fetch, dependencies }. Mutually exclusive with weak.
bundleAlias or inline bundle table, when the spec comes from a bundle.
ref, sha256Pins for the spec source.
optionsPart of the dependency's identity.
needed_bycheck, import, fetch, stage, build, or install. Defaults to build.
productAsk for one product rather than the whole package.
weakFallback entry when the query matches nothing.
setupSelect SETUP pairs by name.

That is the whole list. Since envy 0.3.1 any other key is an error naming the spec and the entry index, and platforms gets its own message: platform filtering is a manifest PACKAGES field, so a conditional dependency is written with if envy.PLATFORM == ... then around the entry.

A source.dependencies entry is narrower still. It takes spec, source, options, product, ref and sha256, and refuses needed_by, which a fetch prerequisite cannot vary. The table under weak is a complete strong declaration, so it carries its own source and refuses bundle, setup, weak, and needed_by, which it inherits from the entry above it.

SETUP pair fields​

FieldRequiredForms
CHECKyesA script string, where exit 0 means satisfied, or a function returning a boolean or a script string.
INSTALLyesA script string, or a function returning nothing or a script string.
PLATFORMSnoPer-pair platform filter.
DEPENDSnoNames of sibling pairs that run first. Selecting a pair selects its prerequisites transitively.

Setup pairs are re-evaluated every run, never cached, and never part of the package key. See The SETUP Verb.

OPTIONS constraint fields​

FieldMeaning
requiredThe option has to be present.
typestring, int, float, boolean, table, list, or semver.
rangeComparison chain for numbers and semver, for example ">=1 <=64".
choicesArray of allowed values.
validateFunction returning nil or true for valid, false, or an error message string.

The table form validates declaratively. The function form receives the options and may call envy.options(schema) to apply the same checks. Both reject options the schema does not declare.

DISPLAY​

Text naming what this package is working on. Requires envy 0.4.2.

A package's progress row has three parts: the [identity], then DISPLAY, then whatever the row is reporting. DISPLAY is added to the identity, not substituted for it. It exists for the case of one spec instantiated several times, where every row shows the same identity:

IDENTITY = "acme.github@r0"
OPTIONS = { repo = { type = "string", required = true },
version = { type = "string", required = true } }

DISPLAY = function(options) return options.repo end
[acme.github@r0] libusb/hidapi 42% [========> ] 4.10MB/9.77MB
[acme.github@r0] cesanta/mongoose 17% [===> ] 1.55MB/9.12MB
[acme.nanoprintf@r1] 3% [> ] 0.20MB/6.40MB
Rule
FormsA string, or a function of the validated options.
ResolvedOnce, after OPTIONS validates, before any fetch.
nilReturning it is the same as omitting DISPLAY entirely.
ContentOne line of printable text. Any control character — newline, tab, NUL, ESC — is an error, since envy measures the row's width in order to erase it later.
WidthPadded to its own column, so the bars and outcomes below it stay aligned. Keep it short; a long one pushes every row's status to the right.

It appears on the outcome line as well, including when envy is not writing to a terminal. That is the case that matters in CI, where without it four instances of one spec produce four identical [acme.github@r0] lines. From envy 0.4.10 it also labels the package's SETUP pair rows.

DISPLAY only labels the rows. A spec instantiated several times must still give each instance its own product names: a fixed PRODUCTS = { mytool = ... } makes the second instance an error. Compute them from the same options DISPLAY uses.

If no spec sets a DISPLAY, output is unchanged from earlier versions and the column is absent.

Identity syntax​

namespace.name@revision

namespace and name are dot-separated identifiers, and revision is a spec revision such as r1 or v1, not the packaged tool's version. The tool version belongs in options. A spec's IDENTITY has to match how the manifest names it, and in a bundle it has to match its SPECS key.

A minimal spec of each kind​

cache-managed
IDENTITY = "acme.ripgrep@r0"
EXPORTABLE = true

FETCH = {
source = "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-apple-darwin.tar.gz",
sha256 = "24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be",
}

STAGE = { strip = 1 }

PRODUCTS = { rg = "rg" }
user-managed
IDENTITY = "acme.build-essential@r1"
USER_MANAGED = true
PLATFORMS = { "linux" }

SETUP = {
build_essential = {
CHECK = "dpkg -s build-essential >/dev/null 2>&1",
INSTALL = "sudo apt-get update && sudo apt-get install -y build-essential",
},
}

See also​