Adding Packages
A cookbook of PACKAGES entries. Every entry is a table, and the fields are
documented in Package Entries.
From a bundle
The common case once a project has a bundle pinned. One ref covers every spec
you take from it:
BUNDLES = {
envy = {
identity = "envy.package-specs@r3",
source = "https://github.com/envy-package-manager/package-specs.git",
ref = "ded36a39bbf13744f5a0e539f2f4741fecb61dd0",
},
}
PACKAGES = {
{ spec = "envy.cmake@r0", bundle = "envy", options = { version = "4.4.0" } },
{ spec = "envy.ninja@r0", bundle = "envy", options = { version = "1.13.2" } },
{ spec = "envy.uv@r0", bundle = "envy", options = { version = "0.11.30" } },
}
From a project-local spec file
For a tool nobody has published a spec for, or one whose spec is specific to your project:
local specs = envy.abspath("envy") .. "/"
PACKAGES = {
{ spec = "local.mytool@r0", source = specs .. "local.mytool.lua" },
}
envy.abspath resolves against the directory of the file that calls it, so the
entry works from any working directory and from a superproject that imports this
manifest. No sha256 is needed, because the file is already versioned by the
commit you are on.
From a URL
PACKAGES = {
{ spec = "acme.protoc-gen-acme@r0",
source = "https://specs.acme.example/protoc-gen-acme@r0.lua",
sha256 = "9f2c1d5b8e47a03f6c2d9b1e4a7f0c3d8b5e2a9f4c1d7b0e3a6f9c2d5b8e1a4f",
options = { version = "2.1.0" } },
}
Pin the hash. Get it with envy hash after
downloading the file once.
From git
PACKAGES = {
{ spec = "acme.jlink@r1",
source = "https://github.com/acme/envy-specs.git",
-- envy git-resolve https://github.com/acme/envy-specs refs/heads/main
ref = "7bc9a0bfe050ef97e1712ff61c6f11952799e951",
options = { version = "9.30" } },
}
A git source requires a full commit sha. Use
envy git-resolve to turn a branch or tag into
one, and leave the command in a comment.
Passing options
Options are ordinary Lua values, and the spec's OPTIONS schema decides which
are legal:
PACKAGES = {
{ spec = "envy.python@r1", bundle = "envy",
options = {
version = "3.13.14", -- string
release = "20260623",
provide_python3 = true, -- boolean
} },
{ spec = "acme.clang-tools@r0", source = specs .. "acme.clang-tools.lua",
options = {
version = "22.1.8",
tools = { "clang-format", "clang-tidy" }, -- list
} },
}
Options are part of the package's identity, which is what lets two entries for one spec coexist:
PACKAGES = {
{ spec = "envy.python@r1", bundle = "envy",
options = { version = "3.13.14", release = "20260623", provide_python3 = true } },
{ spec = "envy.python@r1", bundle = "envy",
options = { version = "3.14.2", release = "20260623" } },
}
Both install. The first claims the python3 product name, the second does not, so
there is no collision. Each also provides its own python3.13 or python3.14.
Restricting to platforms
PACKAGES = {
{ spec = "acme.apt@r0", source = specs .. "acme.apt.lua",
platforms = { "linux" },
options = { packages = { "libusb-1.0-0-dev" } } },
{ spec = "acme.dtrace-tools@r0", source = specs .. "acme.dtrace-tools.lua",
platforms = { "darwin-arm64" } },
}
An entry whose filter excludes the current machine never instantiates, with no error. Naming it explicitly on the wrong platform is an error, so a typo in a CLI query fails loudly.
Filter values are darwin, linux, and windows, optionally with an
architecture: windows-x86_64, windows-arm64, darwin-arm64. A cross-platform
package needs no filter at all, and most do not have one. See
Platforms.
Opting into setup pairs
Nothing in a spec's SETUP runs unless an entry names it:
PACKAGES = {
{ spec = "acme.jlink@r1", source = specs .. "acme.jlink.lua",
options = { version = "9.30" },
setup = { "udev_rules" } },
}
Conditional selection is normal, because the manifest is Lua:
local ci = os.getenv("CI")
PACKAGES = {
{ spec = "acme.apt@r0", source = specs .. "acme.apt.lua",
platforms = { "linux" },
options = { packages = { "libusb-1.0-0-dev" } },
setup = not ci and { "packages" } or nil },
}
Selecting a pair does not change the package's identity, so the same cached artifact serves a machine that selected it and one that did not.
Environment-conditional entries
The manifest is a program. os.getenv and envy.extend are available:
PACKAGES = {
{ spec = "envy.cmake@r0", bundle = "envy", options = { version = "4.4.0" } },
}
if os.getenv("ACME_ENABLE_COVERAGE") then
envy.extend(PACKAGES, {
{ spec = "acme.coverage-tools@r0", source = specs .. "acme.coverage-tools.lua" },
})
end
Use this sparingly. An entry that appears only under an environment variable is an entry most contributors never see.
Verifying the result
$ envy sync
[envy.cmake@r0] installed (8.2s)
deploy: 3 product script(s) (3 created, 0 updated, 0 unchanged, 0 removed)
$ envy product # what the project now offers
cmake bin/cmake envy.cmake@r0{["version"]="4.4.0"}
ctest bin/ctest envy.cmake@r0{["version"]="4.4.0"}
cpack bin/cpack envy.cmake@r0{["version"]="4.4.0"}
$ cmake --version
cmake version 4.4.0
If a product you expected is missing, the spec probably declares it
script = false, which deploys no wrapper on purpose. Ask for it directly with
envy product <name>.
Commit the manifest and the new wrappers together. On a cross-platform repo, add
--platform all so the .bat twins are written and committed too, whichever
machine you happen to be on:
$ envy sync --platform all
deploy: 6 product script(s) (3 created, 0 updated, 3 unchanged, 0 removed)
See also
- Package Entries for the full field list.
- Options for what a spec accepts and why.
- Writing a Spec when no spec exists yet.