resolve

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package resolve answers avar's central question: given where the user is standing and what they typed, which single machine does this invocation target?

The user never names a machine (REQ-1.5), so something has to. This package is that something, and it is the only place that decides:

  • which environment applies, by precedence — explicit flags, then the project's remembered choice, then avar's global configuration, then avar's built-in defaults;
  • which (distro, version, arch) combinations exist at all, and what a bare distribution name means (see matrix.go);
  • what the target machine is called, deterministically, so that the same inputs always reach the same machine and distinct environments never share one (REQ-4.3, PROP-2).

Resolve is a deterministic function of its arguments. It reads neither the environment, the clock, nor the filesystem: everything it needs — the current directory, the host architecture, the global configuration layer — arrives as a parameter. Its only side effects go through the injected Store, which is how a project's first use gets recorded and how `--isolate` is remembered (REQ-11.2). That is what makes PROP-2 checkable against a fake store with no VM in sight.

Nothing here names a virtualization technology, an image, or a command-line tool. The resolver decides *what* environment is wanted and hands the answer to a provider, which decides how to produce it (REQ-17.3). The backend serving the host arrives as a types.ProviderID, so a target records which backend it belongs to without this package knowing anything else about it — including, deliberately, how a host directory looks from inside the guest, which is Provider.MapProjectPath's answer and not the resolver's (REQ-18.5).

Index

Constants

View Source
const DefaultDistro = types.DistroUbuntu

DefaultDistro is the distribution a user who names none gets (REQ-1.2). Its version is that distribution's pinned release, so the default environment is stated in exactly one place: the matrix below.

Variables

View Source
var ErrUnsupportedEnvironment = errors.New("unsupported environment")

ErrUnsupportedEnvironment is wrapped by every error that reports an environment avar cannot run. The command layer distinguishes "avar could not understand you" from "the operation failed" by matching it, so that an unsupported --distro or --arch exits with the usage status and prints the supported values (REQ-4.4).

Functions

func PinnedVersion

func PinnedVersion(d types.Distro) (string, bool)

PinnedVersion returns the release a bare `--distro <name>` resolves to (REQ-4.2), and reports whether the distribution is supported at all.

func SupportedArches

func SupportedArches(d types.Distro, version string) []types.Arch

SupportedArches returns the architectures a given release runs on, in the order they are offered. An unsupported combination has none.

func SupportedDistros

func SupportedDistros() []types.Distro

SupportedDistros returns the distributions avar can run, in the order they are offered to the user.

func SupportedEnvironments

func SupportedEnvironments() []types.EnvironmentSelector

SupportedEnvironments enumerates every (distro, version, arch) combination avar supports, ordered by distribution, then release, then architecture, so that callers and tests see a stable list.

Isolated is false in every entry: isolation is a per-project choice about which machine an environment is served by, not a property of the matrix.

func SupportedVersions

func SupportedVersions(d types.Distro) []string

SupportedVersions returns the releases avar supports for a distribution, newest first. An unknown distribution has none.

Types

type Options

type Options struct {
	// HostArch is the architecture guests run on without emulation. Empty means
	// the architecture avar itself was built for.
	//
	// It is a parameter rather than a lookup so that resolution for an
	// Apple Silicon host and for an Intel host are both testable anywhere,
	// which is what REQ-4.6's emulation reporting needs.
	HostArch types.Arch

	// Config is the global-configuration layer: avar's own `~/.avr/config.toml`
	// defaults, which sit below a project's remembered choice and above avar's
	// built-in defaults.
	//
	// Parsing that file is not this package's business (task 22, REQ-15.1);
	// this field is the seam it lands in. Until then callers leave it zero and
	// the built-in defaults apply, and no part of the precedence chain has to
	// be restructured when it stops being zero.
	Config Preference
}

Options carries what Resolve needs about the world it is resolving in. The zero value means "this host, avar's built-in defaults", which is what production code wants; tests set the fields explicitly so that a result does not depend on which machine the test runs on.

type Preference

type Preference struct {
	// Distro, when set, chooses the distribution.
	Distro types.Distro

	// Version pins Distro's release. It is only meaningful alongside Distro —
	// a version identifies a release of a particular distribution, so a layer
	// that names a version without one is a mistake rather than a preference,
	// and Resolve says so instead of guessing which distribution was meant.
	Version string

	// Arch, when set, chooses the guest architecture.
	Arch types.Arch
}

Preference is one layer of the precedence chain: a partially specified opinion about the environment. A zero-valued field means the layer has no opinion and the layer below it decides.

type ResolvedTarget

type ResolvedTarget struct {
	// Provider is the backend this target belongs to, echoed back from the
	// argument Resolve was given. A machine's identity is per-provider —
	// nothing here is meaningful on a host running a different backend — so a
	// resolved target says which one it was resolved for rather than leaving
	// the caller to remember (design §3.2).
	Provider types.ProviderID

	// Selector is the environment, fully specified: no empty version, no
	// implied architecture, and checked against the supported matrix.
	Selector types.EnvironmentSelector

	// MachineName is the machine this invocation targets, derived
	// deterministically from Selector, and from the project as well when the
	// selector is isolated. It carries avar's ownership prefix and satisfies
	// types.ValidateMachineName.
	MachineName string

	// Kind is the role the machine plays, which follows from the resolved
	// isolation: a per-project machine or the machine every project shares.
	Kind types.MachineKind

	// Project is the record for the project this invocation belongs to, which
	// is not necessarily the directory avr was run from — see the note on
	// project resolution in resolveProject. Its Path is the directory that has
	// to be shared into the guest; its ID identifies an isolated machine's
	// project.
	Project types.ProjectRecord

	// HostCwd is the canonical host directory avr was run from, and it is a
	// host path: it is never passed to a guest as it stands.
	//
	// Turning it into the directory the guest process starts in is the
	// provider's job, through Provider.MapProjectPath — on Lima the answer is
	// the same path, because the project is shared at the identical absolute
	// path, but on WSL it is a path beneath the project's deterministic guest
	// root and a resolver that returned a "guest cwd" would be asserting
	// something only the backend can know (REQ-6.6, REQ-18.5, PROP-1).
	HostCwd string

	// Emulated reports that this environment can only run under CPU emulation
	// on this host, which costs enough performance to be worth telling the user
	// about once, at provision time (REQ-4.6).
	//
	// Resolving never warns — it reports. It is Selector.Emulated() evaluated
	// against the host architecture resolution actually used, so that a
	// resolution performed for a different host stays self-consistent.
	Emulated bool
}

ResolvedTarget is one fully specified answer: everything a caller needs to bring the right machine up and enter it, with nothing left to decide.

func Resolve

func Resolve(provider types.ProviderID, cwd string, sel cli.Selector, st Store, opts Options) (ResolvedTarget, error)

Resolve maps (provider, cwd, flags, state) onto the single machine this invocation targets.

provider is the backend serving this host, chosen from the host platform before resolution begins (design §2, step 2). It is a parameter rather than something the resolver works out, because which backend a host uses is not a question about the user's directory or flags, and because a resolution performed for one backend must stay self-consistent when tested on a machine running another. Machine names are deterministic *within* a provider (design §3.2): a host runs one backend, so nothing has to be added to a name to keep two backends' machines apart, but a resolved target still records which backend it belongs to and so does every machine record.

cwd is the host directory avr was run from, and must be absolute and symlink-resolved — the caller canonicalises it (state.ResolveProjectPath), because a project's identity is the identity of a real directory and resolving it is filesystem work that would make this function impure.

The environment is resolved by precedence, highest first: the flags the user typed, the project's own remembered choices, opts.Config, then avar's built-in defaults. A layer that names a distribution also fixes its version — so `--distro fedora` never inherits Ubuntu's release number from a layer below — and a distribution named without a version gets that distribution's pinned release.

Resolve writes through st: the project is registered on first use, and --isolate is remembered so that a later bare `avr` in the same project goes to the same private machine (REQ-11.2). --shared deliberately writes nothing.

type Store

type Store interface {
	// Projects returns every project avar has recorded. Order does not matter
	// to the resolver.
	Projects() ([]types.ProjectRecord, error)

	// EnsureProject records the directory at path as a project if it is not
	// recorded yet, marks it used now, and returns the record. The returned
	// Path is canonical: absolute, with symlinks resolved.
	EnsureProject(path string) (types.ProjectRecord, error)

	// UpdateProject applies mutate to the record identified by id and stores
	// the result. The record must already exist; mutate must not change the
	// record's identity or path.
	UpdateProject(id string, mutate func(*types.ProjectRecord)) (types.ProjectRecord, error)
}

Store is the part of avar's state the resolver needs, and no more: the project records it reads to honour a remembered choice, plus the two writes that keep those records true.

The interface is declared here, by the consumer, rather than taken as a concrete store. That keeps the resolver testable with a small fake, keeps it from quietly growing a dependency on the rest of the store's surface, and keeps the direction of dependency pointing at avar's own vocabulary rather than at a particular persistence layer. *state.Store satisfies it structurally; the package's tests assert that at compile time, which is deliberately the only place internal/resolve and internal/state meet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL