internal/rosa: create metadata alongside artifact

This enables deferring evaluation of azalea-based packages and fixes the longstanding quirk of version being disjoint from other metadata.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-05-19 00:03:42 +09:00
parent 0e95573f18
commit 8807cbc730
88 changed files with 612 additions and 874 deletions

View File

@@ -70,11 +70,9 @@ type (
// P represents multiple [ArtifactH].
type P []ArtifactH
// Artifact is stage-agnostic immutable data with a deterministic resulting
// [pkg.Artifact]. It can be created natively or through evaluation.
type Artifact struct {
f func(t Toolchain) (a pkg.Artifact, version string)
// Metadata is stage-agnostic immutable data around [Artifact] not directly
// representable in the resulting [pkg.Artifact].
type Metadata struct {
// Unique package name.
Name string `json:"name"`
// Short user-facing description.
@@ -85,6 +83,8 @@ type Artifact struct {
// Runtime dependencies.
Dependencies P `json:"dependencies"`
// Package version.
Version string `json:"version"`
// Project identifier on [Anitya].
//
// [Anitya]: https://release-monitoring.org/
@@ -98,7 +98,7 @@ type Artifact struct {
}
// GetLatest returns the latest version described by v.
func (meta *Artifact) GetLatest(v *Versions) string {
func (meta *Metadata) GetLatest(v *Versions) string {
if meta.latest != nil {
return meta.latest(v)
}
@@ -134,7 +134,7 @@ func (v *Versions) getStable() string {
}
// GetVersions returns versions fetched from Anitya.
func (meta *Artifact) GetVersions(ctx context.Context) (*Versions, error) {
func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) {
if meta.ID == 0 {
return nil, UnpopulatedIDError{}
}
@@ -160,10 +160,13 @@ func (meta *Artifact) GetVersions(ctx context.Context) (*Versions, error) {
return &v, errors.Join(err, resp.Body.Close())
}
// A cachedArtifact holds [pkg.Artifact] and its corresponding version string.
// Artifact is a lazily initialised [pkg.Artifact] with associated [Metadata].
type Artifact func(t Toolchain) (meta *Metadata, a pkg.Artifact)
// A cachedArtifact caches satisfied [Artifact].
type cachedArtifact struct {
a pkg.Artifact
v string
meta *Metadata
a pkg.Artifact
}
const (
@@ -242,20 +245,20 @@ func (s *S) DropCaches(targetArch string, flags int) {
}
}
// Get returns the address of the named [Artifact].
func (s *S) Get(handle ArtifactH) (meta *Artifact) {
// get returns the named [Artifact].
func (s *S) get(handle ArtifactH) (f Artifact) {
s.wantsArch()
v, ok := s.artifacts.Load(handle)
if ok {
meta = v.(*Artifact)
f = v.(Artifact)
}
return
}
// MustGet is like Get, but panics if the named [Artifact] is not registered.
func (s *S) MustGet(handle ArtifactH) (meta *Artifact) {
meta = s.Get(handle)
if meta == nil {
// mustGet is like get, but panics if the named [Artifact] is not registered.
func (s *S) mustGet(handle ArtifactH) (f Artifact) {
f = s.get(handle)
if f == nil {
panic(HandleError(handle))
}
return
@@ -282,8 +285,8 @@ func (e LoadError) Error() string {
return "cannot load " + strconv.Quote(e.Handle.String()) + ": " + e.Err.Error()
}
// Load returns the resulting [pkg.Artifact] of [ArtifactH].
func (t Toolchain) Load(handle ArtifactH) (pkg.Artifact, string) {
// Load satisfies an [Artifact] referred to by an [ArtifactH].
func (t Toolchain) Load(handle ArtifactH) (*Metadata, pkg.Artifact) {
defer func() {
r := recover()
if r == nil {
@@ -305,36 +308,36 @@ func (t Toolchain) Load(handle ArtifactH) (pkg.Artifact, string) {
e, ok := t.c[t.stage].Load(handle)
if ok {
r := e.(cachedArtifact)
return r.a, r.v
return r.meta, r.a
}
meta := t.Get(handle)
if meta == nil {
return nil, ""
f := t.get(handle)
if f == nil {
return nil, nil
}
var r cachedArtifact
r.a, r.v = meta.f(t)
r.meta, r.a = f(t)
t.c[t.stage].Store(handle, r)
return r.a, r.v
return r.meta, r.a
}
// MustLoad is like Load, but panics if the named [Artifact] is not registered.
func (t Toolchain) MustLoad(handle ArtifactH) (pkg.Artifact, string) {
a, version := t.Load(handle)
if a == nil {
func (t Toolchain) MustLoad(handle ArtifactH) (*Metadata, pkg.Artifact) {
meta, a := t.Load(handle)
if meta == nil {
panic(HandleError(handle))
}
return a, version
return meta, a
}
// Register arranges for a new [Artifact] to be cured under s. It returns false
// if another [Artifact] is already registered under the same name.
func (s *S) Register(meta *Artifact) bool {
if meta.Name == "" {
func (s *S) Register(name string, f Artifact) bool {
if name == "" {
return false
}
p := ArtifactH(unique.Make(meta.Name))
_, ok := s.artifacts.LoadOrStore(p, meta)
p := ArtifactH(unique.Make(name))
_, ok := s.artifacts.LoadOrStore(p, f)
if !ok {
s.artifactCount.Add(1)
}
@@ -346,16 +349,34 @@ func (s *S) Register(meta *Artifact) bool {
type RegisterError ArtifactH
func (e RegisterError) Error() string {
if ArtifactH(e).String() == "" {
return "attempting to register invalid name"
}
return "attempting to register " + strconv.Quote(ArtifactH(e).String()) + " twice"
}
// MustRegister is like Register, but panics if registration fails.
func (s *S) MustRegister(meta *Artifact) {
if !s.Register(meta) {
panic(RegisterError(H(meta.Name)))
func (s *S) MustRegister(name string, f Artifact) {
if !s.Register(name, f) {
panic(RegisterError(H(name)))
}
}
// mustRegister registers an [Artifact] with the old function signature.
//
// Deprecated: Artifacts should be migrated to Register.
func (s *S) mustRegister(
f func(t Toolchain) (pkg.Artifact, string),
meta *Metadata,
) {
s.MustRegister(meta.Name, func(t Toolchain) (*Metadata, pkg.Artifact) {
v := *meta
a, version := f(t)
v.Version = version
return &v, a
})
}
// Count returns the number of [Artifact] registered to s.
func (s *S) Count() int {
return int(s.artifactCount.Load())
@@ -567,7 +588,7 @@ func (ctx *evalContext) f(
return unique.Make(azalea.Ident(name))
}
meta := Artifact{Name: string(name)}
meta := Metadata{Name: string(name)}
var (
attr PackageAttr
patches []string
@@ -575,7 +596,6 @@ func (ctx *evalContext) f(
early bool
anitya int64
version string
sourceA any
helper Helper
@@ -584,11 +604,9 @@ func (ctx *evalContext) f(
if err = args.Apply(map[unique.Handle[azalea.Ident]]any{
k("description"): &meta.Description,
k("website"): &meta.Website,
k("version"): &meta.Version,
k("anitya"): &anitya,
k("version"): &version,
k("source"): &sourceA,
k("writable"): &attr.Writable,
k("chmod"): &attr.Chmod,
k("enterSource"): &attr.EnterSource,
@@ -598,6 +616,7 @@ func (ctx *evalContext) f(
k("exclusive"): &excl,
k("toyboxEarly"): &early,
k("source"): &sourceA,
k("exec"): &helper,
k("inputs"): &inputs,
k("runtime"): &runtimes,
@@ -630,7 +649,7 @@ func (ctx *evalContext) f(
}
meta.ID = int(anitya)
meta.f = func(t Toolchain) (pkg.Artifact, string) {
v = Artifact(func(t Toolchain) (*Metadata, pkg.Artifact) {
var source pkg.Artifact
switch p := sourceA.(type) {
case pkg.Artifact:
@@ -646,17 +665,15 @@ func (ctx *evalContext) f(
})
}
return t.NewPackage(
return &meta, t.NewPackage(
meta.Name,
version,
meta.Version,
source,
&attr,
helper,
inputsH...,
), version
}
v = meta
)
})
set = true
return
}
@@ -686,13 +703,13 @@ func (s *S) Evaluate(r io.Reader, b fs.FS) error {
ctx := evalContext{b}
for _, f := range pending {
meta, set, err := azalea.Evaluate[Artifact](ctx.f, s.getS(), *f)
lf, set, err := azalea.Evaluate[Artifact](ctx.f, s.getS(), *f)
if err != nil {
return err
} else if !set {
return errors.New("unexpected unset")
}
if !s.Register(&meta) {
if !s.Register(string(f.Ident), lf) {
return RegisterError(H(string(f.Ident)))
}
}