Skip to main content

The Package Lifecycle

What happens between "listed in the manifest" and "ready to use". Every verb plugs into one slot of this pipeline, and most slots are usually empty.

spec acquired → check cache → depot import → FETCH → STAGE → BUILD → INSTALL → SETUP pairs
│ │
│ └─ prebuilt artifact found: skip to SETUP
└─ finished package already in cache: skip everything

The two short circuits matter more than the verbs. On a warm machine most packages are cache hits, and in a project with a depot most misses are downloads rather than builds.

The phases​

PhaseWhat it does
spec fetchAcquire the spec from disk, a URL, git, or a bundle, then execute it and validate its globals. OPTIONS runs here, so a bad option fails before anything is downloaded, and DISPLAY is resolved right after it, so every later row can carry it.
checkHash the package identity: identity, serialized options, and any resolved weak dependency keys, as blake3. The hash names the cache entry. A completed entry ends the run for this package.
importAsk the depot for a prebuilt artifact matching that hash. A hit is downloaded, verified against its sha256, and unpacked. No fetch, no build.
FETCHGet the bytes.
STAGEArrange a working tree.
BUILDTransform it.
INSTALLProduce the final package directory.
SETUPAdjust the host machine, for the pairs a manifest entry selected, and only when their CHECK reports the host is not already correct.
exportOnly during envy export: archive the finished package and print its index line.
vendorOnly when the manifest asked: copy the package into the project tree, or confirm the copy already there still matches. envy vendor runs the ladder for the sake of this step. Requires envy 0.4.0.

The directories a spec sees​

envy creates one cache entry per package, at <cache>/packages/<identity>/<platform>-<arch>-blake3-<hash>/:

DirectoryPassed to verbs asLifetime
fetch/fetch_dirDownloads. Survives a failed attempt so a retry does not re-download. Deleted once the package completes, unless the spec is not EXPORTABLE, in which case the artifacts are kept for a depot to publish.
work/stage/stage_dirScratch working tree. Deleted on success and on failure.
work/tmp/tmp_dirScratch space for anything a verb writes and does not keep. Deleted with work/.
pkg/install_dirThe package. Products resolve into it, other projects share it, and a depot ships it.

Paths arrive with a trailing separator applied, so fetch_dir .. "tool.tar.gz" is correct. The separator is the platform's, so that concatenation produces .../fetch/tool.tar.gz on macOS and Linux and C:\...\fetch\tool.tar.gz on Windows. Prefer envy.path.join when the result goes into a script, and never hardcode /.

Where extraction lands​

Default staging extracts fetched archives straight into pkg/. A spec that only downloads and unpacks pays for no intermediate copy. envy uses work/stage/ instead when STAGE, BUILD, or INSTALL is a function. That is the case where a spec inspects the tree before deciding what the package should be.

-- Extracts into pkg/ directly: nothing here is a function.
STAGE = { strip = 1 }
INSTALL = "make install"

-- Extracts into work/stage/, because INSTALL is a function that copies out of it.
STAGE = { strip = 1 }
INSTALL = function(install_dir, stage_dir) envy.copy(stage_dir .. "bin/tool", install_dir) end

Where scripts run​

A string verb, or a string returned from a function verb, is a shell script. Each verb starts it in a specific directory:

VerbScript's working directory
STAGEthe staging destination: work/stage/, or pkg/ per the rule above
BUILDwork/stage/
INSTALLwork/stage/
SETUP CHECK and INSTALLthe project root, because they act on host state

Parallelism and ordering​

Packages run concurrently, each on its own thread. Only a declared dependency serializes them. A dependency can demand readiness as early as "before I can fetch", which is how a spec that needs a download tool gets one. See Phase Ordering and needed_by.

Concurrent envy processes are also safe. Cache entries are file-locked, so two envy sync runs in two terminals cooperate.

Completion is final​

A completed entry carries an envy-complete marker, and envy never re-validates it. No timestamps, no re-hashing, no revalidation pass. A package stays done until either its identity changes, which names a different entry, or you delete the cache.

That is why envy sync is cheap to run repeatedly, and why deleting the cache is always safe.

When something fails​

OutcomeWhat happens to the entry
Successwork/ is removed and the marker is written. fetch/ is removed too, unless the spec is not EXPORTABLE.
A verb failspkg/ and work/ are removed. fetch/ is kept, so the next attempt resumes from downloaded bytes. The entry stays unmarked, so the next run redoes the work.
Nothing had happened yetThe whole entry is removed.
A SETUP pairIts lock entry is ephemeral and always purged. Host state has no cache, and the pair's CHECK is the only re-run gate.

A partially installed package is never left where a consumer can resolve into it. envy writes the marker last, after INSTALL reports success.

See also​