resolve

package
v0.5.0-1a Latest Latest
Warning

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

Go to latest
Published: May 31, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package resolve handles import resolution and lock file management.

Resolves import paths (bare URLs, with // subdirs, local ./ paths) to concrete sources. Detects whether each import is a UB library (it has kind-prefixed body files at the root) or a Go library. Detects cycles at resolve time and reports them as compile errors. Enforces same-repo imports sharing a version.

Reads and writes unobin.lock - pins git commits and content hashes per import for reproducible compiles.

Index

Constants

View Source
const CurrentLockVersion = 1

CurrentLockVersion is the schema version this package reads and writes. Older versions error on read; newer versions error too, since this build can't guarantee correct interpretation.

View Source
const LockFileName = "unobin.lock"

LockFileName is the standard filename for a stack or library's lock file.

Variables

View Source
var ErrEmptyImportRef = errors.New("empty import reference")

ErrEmptyImportRef is returned when the source string is empty.

View Source
var ErrRemoteNotImplemented = errors.New("remote resolver not implemented")

ErrRemoteNotImplemented is retained for callers that switched on it while the resolver was a stub. New callers should not depend on it.

Functions

func CheckSameRepoVersions

func CheckSameRepoVersions(refs map[string]ImportRef) []error

CheckSameRepoVersions returns one error per repo URL where two remote imports disagree on version. Local imports are ignored. Results are sorted by URL so callers see deterministic output.

Same-repo imports must share a version because the underlying repo is fetched once: a single resolved commit must satisfy every alias that points into it.

func ContainsMainUB

func ContainsMainUB(s *Source) bool

ContainsMainUB reports whether s has a `main.ub` at its root, which marks the directory as a factory: runnable and not importable.

func EncodeLockFile

func EncodeLockFile(lock *LockFile) ([]byte, error)

EncodeLockFile serializes lock to bytes with a trailing newline.

func ExtractImports

func ExtractImports(f *lang.File) (map[string]ImportRef, []error)

ExtractImports walks the `imports:` block of f and parses each value into an ImportRef. Returns a map of alias to ref plus any per-import parse errors. Shape problems with the block itself are reported by `lang.ValidateImports` and silently skipped here so the two passes don't both report the same errors.

func IsUBLibrary

func IsUBLibrary(s *Source) bool

IsUBLibrary reports whether s is a UB-implemented library: a directory holding at least one `.ub` file and no `main.ub` (a `main.ub` marks a factory, which is not importable). Every `.ub` file in a library is expected to be a kind-prefixed body (`resource-*.ub`, `data-*.ub`, or `action-*.ub`); a misnamed one is caught when the library is parsed, not here, so the author gets a clear error rather than having the whole directory silently treated as a Go library. Sources with no `.ub` files are Go libraries.

func ResolveImports

func ResolveImports(f *lang.File, resolver Resolver) (map[string]*ResolvedImport, []error)

ResolveImports extracts imports from f and runs each through resolver. The returned map preserves every alias even when its Source is nil (resolver error). Errors are collected from extraction, same-repo version checking, and the resolver itself.

func UBKey

func UBKey(ref ImportRef) string

UBKey is the dedup key for a UB-library import. Remote imports key on URL, subdir, and version; the `//<subdir>` segment is included only when the import names a subdirectory, so root-of-repo refs read cleanly in cycle errors and other diagnostics. Local imports key on path.

func ValidateCompositeBody

func ValidateCompositeBody(kind, typeName string, f *lang.File) []error

ValidateCompositeBody checks a composite body against the floor and ceiling rules for its kind, which comes from the file's `<kind>-` name prefix:

  • data: at least one output, may hold data, no resources, no actions.
  • action: at least one action, may hold data, no resources; outputs are optional.
  • resource: at least one resource, may hold data and actions; outputs are optional.

typeName names the composite in the messages. Returns one error per violated rule, in a fixed order, so a body reports every problem at once. The resolver does not run this during the walk; the compile command runs it over each resolved library so that print-graph and fetch stay lenient.

func WriteLockFile

func WriteLockFile(path string, lock *LockFile) error

WriteLockFile serializes lock and atomically replaces path. The output is JSON pretty-printed with two-space indent for readable diffs. Map keys are sorted by encoding/json so the bytes are deterministic.

Types

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph models import dependencies among UB files. Nodes are opaque string IDs assigned by the caller - typically a canonical filesystem path for local sources or `url@commit/subdir` for remote ones. Edges point from a file to each file it imports.

func NewGraph

func NewGraph() *Graph

NewGraph returns an empty graph.

func ResolveAll

func ResolveAll(id string, f *lang.File, resolver Resolver) (*Graph, []error)

ResolveAll walks a starting file's imports and every UB library reachable through them, building a `Graph` and returning every error encountered. Pass a stable id for the starting file and a `Resolver` for top level imports. Local imports inside a UB library are resolved against that library's `fs.FS` root.

The graph's node ids are: the caller-supplied id for the starting file; for each imported UB library, "<parent-id>/<alias>"; for each exported file inside a UB library, "<library-id>:<export-name>".

func (*Graph) AddEdge

func (g *Graph) AddEdge(from, to string)

AddEdge records that `from` imports `to`. Duplicate edges are coalesced; both endpoints are registered if missing.

func (*Graph) AddNode

func (g *Graph) AddNode(id string)

AddNode registers id. AddEdge calls AddNode on both endpoints, so AddNode is only needed for isolated nodes (e.g., a leaf with no imports).

func (*Graph) DetectCycles

func (g *Graph) DetectCycles() [][]string

DetectCycles returns every cycle in the graph. Each cycle is the sequence of node ids encountered along a back-edge, starting and ending at the same node. An acyclic graph returns nil.

type ImportRef

type ImportRef interface {
	// contains filtered or unexported methods
}

ImportRef is a parsed value from an `imports:` block.

func ParseImportRef

func ParseImportRef(raw string) (ImportRef, error)

ParseImportRef parses a string from an `imports:` block. Local imports start with `.` or `/`; remote imports name a repo URL plus a required `@version` suffix and use the Terraform-style `//` separator to denote a subdirectory within the repo. Without `//` the whole path before `@` is the repo URL and the import has no subdir.

type LocalImport

type LocalImport struct {
	Path string
}

LocalImport names a sibling on the operator's filesystem. Local imports do not have pinned versions; their content is whatever the developer has at the path now. Compile from a clean checkout to make the result reproducible.

type LocalResolver

type LocalResolver struct {
	Root string
}

LocalResolver resolves *LocalImport refs against a working directory root. Relative paths in the import are joined to Root.

func NewLocalResolver

func NewLocalResolver(root string) *LocalResolver

NewLocalResolver returns a LocalResolver rooted at root. Pass the directory containing the main.ub or library type bodies that own the imports.

func (*LocalResolver) Resolve

func (r *LocalResolver) Resolve(ref ImportRef) (*Source, error)

Resolve implements Resolver. The ref must be a *LocalImport; remote refs return an error so a misrouted call surfaces clearly.

type LockEntry

type LockEntry struct {
	Kind           LockKind `json:"kind"`
	URL            string   `json:"url"`
	Subdir         string   `json:"subdir,omitempty"`
	Constraint     string   `json:"constraint"`
	ResolvedCommit string   `json:"resolved-commit"`
	SubdirHash     string   `json:"subdir-hash"`
}

LockEntry records one resolved import.

type LockFile

type LockFile struct {
	Version int                   `json:"version"`
	Imports map[string]*LockEntry `json:"imports"`
}

LockFile is the on-disk schema for `unobin.lock`. Pins each import's resolved git commit and a content hash of the relevant subdirectory so compiles are reproducible across machines.

func DecodeLockFile

func DecodeLockFile(b []byte) (*LockFile, error)

DecodeLockFile parses a lock file from bytes.

func NewLockFile

func NewLockFile() *LockFile

NewLockFile returns an empty lock at the current schema version.

func ReadLockFile

func ReadLockFile(path string) (*LockFile, error)

ReadLockFile parses the lock at path. If the file does not exist, the underlying error wraps fs.ErrNotExist so callers can detect it with errors.Is.

type LockKind

type LockKind string

LockKind records whether an imported library is Go- or UB-implemented in the lock.

const (
	LockKindGoLibrary LockKind = "go-library"
	LockKindUBLibrary LockKind = "ub-library"
)

type RemoteImport

type RemoteImport struct {
	URL     string
	Subdir  string
	Version string
}

RemoteImport names an importable repo by host + owner/name, optional subdir within the repo, and a version constraint. Constraint format is not parsed here (semver tags, branch names, etc. all flow through as strings) and the resolver enforces what's allowed.

type RemoteResolver

type RemoteResolver struct {
	CacheRoot string
}

RemoteResolver resolves *RemoteImport refs by fetching the named git repo at the requested constraint, caching the working tree under CacheRoot, and exposing the requested subdir as a Source.

CacheRoot is the directory holding `imports/<host>/<path>/<commit>/`. `NewRemoteResolver` defaults it to `<user-cache-dir>/unobin`.

func NewRemoteResolver

func NewRemoteResolver() (*RemoteResolver, error)

NewRemoteResolver returns a RemoteResolver with CacheRoot set to the user's cache directory (XDG_CACHE_HOME or its platform default) joined with `unobin`.

func (*RemoteResolver) Resolve

func (r *RemoteResolver) Resolve(ref ImportRef) (*Source, error)

Resolve fetches the repo named by ref, caches it, and returns a Source rooted at the import's subdir, with FS and Commit always set. A UB library (one with kind-prefixed body files at the subdir root) also gets its content Hash set for lock-file integrity.

type Resolution

type Resolution struct {
	Kind         ResolutionKind
	LocalAlias   string
	Ref          ImportRef
	Path         string
	Version      string
	CanonicalKey string
	SourcePath   string
}

Resolution describes one import after the walker reaches it. For Go imports, Path is the canonical Go-import path (URL plus subdir when present) and Version is the pinned version. For UB imports, CanonicalKey is the dedup key (see UBKey) and visitors look up their per-library state by that key. SourcePath is the on-disk directory where the resolver fetched the import, useful for compile-time inspection.

func WalkUB

func WalkUB(
	refs map[string]ImportRef, resolver Resolver, v UBVisitor,
) ([]Resolution, error)

WalkUB walks refs and every UB library they transitively reach, invoking the visitor for each import. The returned slice mirrors refs in resolved form, alias-sorted, so callers can build their own alias-to-resolution map without per-site visitor callbacks. Cycles through UB libraries are reported as errors.

type ResolutionKind

type ResolutionKind int

ResolutionKind tags how an import was resolved.

const (
	// ResolutionGo names a Go-library import: a remote ref whose resolved
	// source has no kind-prefixed body files at its root.
	ResolutionGo ResolutionKind = iota + 1
	// ResolutionUB names a UB-library import: a ref whose resolved source
	// has kind-prefixed body files at its root.
	ResolutionUB
)

type ResolvedImport

type ResolvedImport struct {
	Ref    ImportRef
	Source *Source
}

ResolvedImport pairs a parsed ImportRef with the Source the resolver returned for it. Source is nil when the resolver errored on this alias. The corresponding error is in the returned slice.

type Resolver

type Resolver interface {
	Resolve(ref ImportRef) (*Source, error)
}

Resolver turns an ImportRef into a Source. Implementations cover one kind of import each (local filesystem, remote git, etc.); callers dispatch by type-switching on the ref.

type Source

type Source struct {
	FS     fs.FS
	Path   string
	Commit string
	Hash   string
}

Source is the file tree of a resolved import, rooted at the import's subdirectory, or the repo root when there is no subdir. For remote imports, Commit and Hash record the resolved git commit and a content hash so the lock file can pin reproducibility. Local imports leave both empty since their content is whatever the developer has now. Path is the on-disk directory the source was fetched into, which the dev CLI uses for compile-time inspection of Go-library source.

type UBLibrary

type UBLibrary struct {
	Bodies      map[string]*lang.File
	Kinds       map[string]string
	BodyImports map[string][]Resolution
}

UBLibrary has everything the visitor needs about a UB library the first time the walker reaches it. Bodies maps composite type name to the parsed body file; the type name comes from a kind-prefixed filename (`<kind>-<type>.ub`). Kinds maps the same type name to its kind (`resource`, `data`, or `action`). BodyImports maps the type name to the resolved imports declared by that body, in alias-sorted order so callers see a stable view across runs.

type UBVisitor

type UBVisitor interface {
	// OnGoImport is called for every site whose import resolves to a
	// Go library. May fire multiple times with the same path when the
	// same library is imported from several sites; visitors that need
	// uniqueness dedup themselves.
	OnGoImport(alias, path, version string) error
	// OnUBLibrary is called once per canonical key. alias is the local
	// alias of whichever site first reached the library (which matters
	// when the visitor names a directory or package after it).
	OnUBLibrary(alias, canonicalKey string, ref ImportRef, lib *UBLibrary) error
}

UBVisitor is implemented by callers that want to consume the walked import graph. The walker invokes its methods as it descends.

Jump to

Keyboard shortcuts

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