Spec Reference
Terse companion to Anatomy of a Spec.
Globals
| Global | Allowed forms | When omitted |
|---|---|---|
IDENTITY | "namespace.name@revision" | error, always required |
FETCH | string, table, array of tables, or function | error, unless USER_MANAGED |
STAGE | table or function | every fetched archive is extracted |
BUILD | string or function | nothing runs |
INSTALL | string or function | the staged tree becomes the package |
SETUP | table of named pairs | no host-side work |
OPTIONS | schema table or function | options are accepted unvalidated |
PRODUCTS | table or function | the package exports nothing |
DEPENDENCIES | array of entry tables | none |
PLATFORMS | array of strings, darwin, linux, windows, optionally -arm64 or -x86_64 | every platform |
DISPLAY | string or function of the options | the package's rows say nothing beyond their identity. Requires envy 0.4.2 |
USER_MANAGED | boolean or function | false, meaning cache-managed |
EXPORTABLE | boolean | false, so only fetched bytes are kept for export |
VENDOR | array of selector strings | the 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:
| Verb | Returns |
|---|---|
FETCH | nothing, or a URL string, optionally followed by a sha256 |
STAGE, BUILD, INSTALL | nothing |
SETUP.<name>.CHECK | true for satisfied, false for not, or a script string to run |
SETUP.<name>.INSTALL | nothing, or a script string to run |
PRODUCTS | the products table |
OPTIONS | nothing, true, false, or an error message string |
DISPLAY | one line of printable text, or nil for none |
USER_MANAGED | boolean |
FETCH table fields
| Field | Meaning |
|---|---|
source | URL, local path, or git URL. Required. |
sha256 | Verifies the download, and lets the cache reuse it. |
ref | Commit for a git source. |
dest | Rename the downloaded file. A plain filename, no separators. |
post_data | HTTP POST body. HTTP and HTTPS only. |
An array of these tables fetches several files.
STAGE table fields
| Field | Meaning |
|---|---|
strip | Drop this many leading path components. |
only | Extract 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 }.
| Field | Default | Meaning |
|---|---|---|
value | required | Path relative to the package directory, or an arbitrary string. |
script | true | Whether to deploy a wrapper script into the project's bin directory. |
platforms | all | Restrict this product to some platforms. |
DEPENDENCIES entry fields
| Field | Notes |
|---|---|
spec | Identity. Required, except for a product-only reference. |
source | URL, path, git URL, or { fetch, dependencies }. Mutually exclusive with weak. |
bundle | Alias or inline bundle table, when the spec comes from a bundle. |
ref, sha256 | Pins for the spec source. |
options | Part of the dependency's identity. |
needed_by | check, import, fetch, stage, build, or install. Defaults to build. |
product | Ask for one product rather than the whole package. |
weak | Fallback entry when the query matches nothing. |
setup | Select 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
| Field | Required | Forms |
|---|---|---|
CHECK | yes | A script string, where exit 0 means satisfied, or a function returning a boolean or a script string. |
INSTALL | yes | A script string, or a function returning nothing or a script string. |
PLATFORMS | no | Per-pair platform filter. |
DEPENDS | no | Names 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
| Field | Meaning |
|---|---|
required | The option has to be present. |
type | string, int, float, boolean, table, list, or semver. |
range | Comparison chain for numbers and semver, for example ">=1 <=64". |
choices | Array of allowed values. |
validate | Function 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 | |
|---|---|
| Forms | A string, or a function of the validated options. |
| Resolved | Once, after OPTIONS validates, before any fetch. |
nil | Returning it is the same as omitting DISPLAY entirely. |
| Content | One 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. |
| Width | Padded 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
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" }
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
- Anatomy of a Spec for the concepts and every verb form
- Writing a Spec for the tutorial
- Lua API
- Manifest Reference