Skip to content

Dependencies and the lock file

Beans dependencies come from Git. There is no central registry. You name a Git host path and a reference in beans.pot, and Beans clones it into a local cache and records the exact result in beans.lock.

Use pot add from the project root:

Terminal window
beansc pot add acme/http v1.2

This adds a require line to your manifest:

module shop
require github.com/acme/http v1.2
  • The path is a Git host path: exactly host/owner/repo.
  • The reference is a tag, branch, or commit-like ref (here, v1.2).
  • You may have many require lines.
  • The same path pinned at two different refs is an error.

owner/repo is GitHub shorthand. Full host paths and HTTPS/SSH Git URLs work too. The ref defaults to HEAD when you leave it out.

Refs are not limited to tags. Branches and commit hashes work too. The lock always records the exact resolved commit and tree. Updating a branch or HEAD may move the lock forward; updating a fixed commit stays on that commit.

For private repositories, authenticate Git itself. Beans never writes a token or password to the manifest or lock file.

Once required, you import packages from it by their full import path, for example import github.com/acme/http. See Local packages and imports for how import paths resolve.

A repository may hold more than one module

Section titled “A repository may hold more than one module”

A subdirectory of a dependency that has its own beans.pot is its own module, not a package of the one above it. One require row reaches both:

require github.com/acme/http v1.2
import github.com/acme/http // binds `http`
import github.com/acme/http/app // binds whatever app/beans.pot declares

The binding is always the name the manifest at that directory declares, never the last path segment — the same answer a require path row pointing at that directory would give. A nested module’s own require rows are read too, so its dependencies come along.

Those rows are resolved for the whole build, not per module, so a dependency required at two different refs anywhere in the graph is an error. When you name a dependency your dependency already names, pin it at the same ref.

beans.lock sits next to beans.pot at the module root. It records the exact commit and tree each dependency resolved to, so a later build gets the same bytes.

It is line-based. The first line is the format version. Then there is one row per resolved dependency, sorted:

version 1
module github.com/acme/http v1.2 3f1c9a2b... 8ad00e1f...

Each dependency row is:

module <path> <requested-ref> <commit> <tree>
  • <path>: the module path from require.
  • <requested-ref>: the ref you asked for (v1.2).
  • <commit>: the exact Git commit SHA it resolved to.
  • <tree>: the Git tree hash of that commit’s contents.

The file is written safely: Beans writes a temporary file and then atomically renames it into place, so you never see a half-written lock.

A build never writes it. Reading a dependency is not a decision about which version to use, so only the commands that make that decision touch the file:

Commit beans.lock and pin the compiler to keep compiler and dependency versions consistent between builds. See Reproducible builds for --locked and --offline, and Compatibility for why pinning matters before 1.0.