SETUP
Post-install work on the host machine, for things that cannot live inside an immutable package directory: udev rules, OS packages, license activation, a system daemon.
SETUP is a table of named CHECK/INSTALL pairs. CHECK asks whether the host
is already correct. INSTALL makes it correct. Nothing in SETUP runs unless a
manifest entry names the pair.
SETUP = {
udev_rules = {
PLATFORMS = { "linux" },
CHECK = function(pkg_dir, opts)
local r = envy.run("cmp -s " .. pkg_dir .. "99-jlink.rules /etc/udev/rules.d/99-jlink.rules",
{ quiet = true, check = false })
return r.exit_code == 0
end,
INSTALL = function(pkg_dir, opts)
envy.run({
"sudo cp " .. pkg_dir .. "99-jlink.rules /etc/udev/rules.d/",
"sudo udevadm control -R",
}, { interactive = true })
end,
},
}
PACKAGES = {
{ spec = "acme.jlink@r1", source = specs .. "acme.jlink.lua",
options = { version = "9.30" },
setup = { "udev_rules" } },
}
Pair fields
| Field | Required | Meaning |
|---|---|---|
CHECK | yes | Is the host already satisfied? |
INSTALL | yes | Make it satisfied. |
PLATFORMS | no | Restrict the pair, for example { "linux" }. A pair that does not match is skipped rather than failed. |
DEPENDS | no | Names of sibling pairs that must complete first. Unknown names and cycles are rejected when the spec loads. |
Any other field is an error, and so is a top-level CHECK outside a pair. Pair
names may contain alphanumerics, _, ., and -.
Verb shapes
| Verb | string | function |
|---|---|---|
CHECK | Shell command. Exit 0 means already satisfied. | CHECK(pkg_dir, opts) returning true or false, or a string that runs as a shell command whose exit status decides. |
INSTALL | Shell script. | INSTALL(pkg_dir, opts) returning nothing, or a string that runs as a shell script. |
pkg_dir is the installed package directory, with a trailing separator, for a
cache-managed package. It is nil for a user-managed one,
which has no package tree.
Both verbs run with the working directory set to the project root rather than a cache directory, because host state is the subject.
A CHECK as a bare string is often enough:
SETUP = {
brew = {
CHECK = "brew --version",
INSTALL = function(pkg_dir, opts)
envy.run({
"sudo -v",
'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash',
}, { env = { NONINTERACTIVE = "1" }, interactive = true })
end,
},
}
Pass interactive = true to envy.run when the command needs the terminal, for
example a sudo password prompt or a license agreement. Output streams through
instead of being captured.
envy gives the child the terminal for as long as it runs, and prints nothing
else meanwhile. Before envy 0.4.3 queued log lines were still flushed during
that time, so under --verbose another package's debug output could print over
the prompt the child was waiting on.
How a pair executes
- Platform filter. A
PLATFORMSmismatch skips the pair. CHECK. If satisfied, nothing runs.- Lock. envy takes a cross-process lock for this
(package, pair), so two concurrentenvy syncruns cannot both install the same udev rule. CHECKagain, because the other process may have just done the work.INSTALL.
A pair is therefore idempotent, and CHECK is the only re-run gate. Nothing
about a pair is cached: its lock entry is ephemeral and always purged, so every
sync re-asks the question. Keep CHECK a fast query rather than a full audit.
While INSTALL runs, its row shows the package's [identity] and the spec's
DISPLAY. On success the row goes
away and the package row says setup complete. On failure the row keeps its
last output, and the error names the pair:
error: SETUP.udev_rules.INSTALL shell script failed for acme.jlink@r1 (exit code 1)
Before envy 0.4.10 the label also held the options and pair name, as in
[acme.jlink@r1{["version"]="9.30"}#setup:udev_rules].
Selection lives in the manifest
Pairs are opt-in per project, by name:
PACKAGES = {
{ spec = "acme.jlink@r1", source = specs .. "acme.jlink.lua",
options = { version = "9.30" } },
}
PACKAGES = {
{ spec = "acme.jlink@r1", source = specs .. "acme.jlink.lua",
options = { version = "9.30" },
setup = not os.getenv("CI") and { "udev_rules" } or nil },
}
Selections union across everyone who asks. A spec can demand a pair from one of its own dependencies:
DEPENDENCIES = {
{ spec = "acme.brew@r0", source = "acme.brew.lua", setup = { "brew" } },
}
So a spec that installs a Homebrew formula can insist Homebrew exists first,
without the project needing to know. Selecting a pair also selects everything it
DEPENDS on.
Per-platform pairs
One spec can carry a pair per platform and let the filter decide. PLATFORMS on
the pair is independent of the spec's own:
SETUP = {
udev_rules = {
PLATFORMS = { "linux" },
CHECK = "test -f /etc/udev/rules.d/99-acme.rules",
INSTALL = "sudo cp packaging/99-acme.rules /etc/udev/rules.d/",
},
driver = {
PLATFORMS = { "windows" },
CHECK = 'pnputil /enum-drivers | Select-String -Quiet "acme.inf"',
INSTALL = function(pkg_dir, opts)
envy.run('pnputil /add-driver "' .. envy.path.join(pkg_dir, "driver", "acme.inf") .. '" /install',
{ interactive = true })
end,
},
}
A manifest can then select { "udev_rules", "driver" } unconditionally, and each
runs only where it applies. Remember that the strings run under the platform
default interpreter, bash on POSIX and PowerShell on Windows, so
Select-String and test -f never meet each other. Anything needing elevation,
sudo or a UAC prompt, wants interactive = true.
Selection does not change identity
A pair selection is not part of the package's identity or hash. The same cached
package, and the same depot artifact, serves a machine that selected
udev_rules and one that did not. The selection only decides which host-side
questions get asked.
Options behave the opposite way. Change an option and you get a different package. Change a setup selection and you get the same package, checked differently.
Pure-setup specs
A spec can be nothing but pairs, with no fetch, no build, and nothing cached. That is a user-managed package: Homebrew itself, apt packages, a kernel module.
See also
- User-Managed Packages for specs that are only
SETUP. - Shells & Scripts for
envy.runoptions, includinginteractive. - Package Entries for the
setup = { ... }field.