forked from rosa/hakurei
internal/rosa: initial azalea bindings
Supported fields are still rather minimal, but evaluation works, and resulting artifacts cure correctly. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -140,6 +140,8 @@ var (
|
|||||||
// IdentInputs is a special array argument in a package declaration whose
|
// IdentInputs is a special array argument in a package declaration whose
|
||||||
// values of [Ident] are kept as is when passed to a [PF].
|
// values of [Ident] are kept as is when passed to a [PF].
|
||||||
IdentInputs = unique.Make(Ident("inputs"))
|
IdentInputs = unique.Make(Ident("inputs"))
|
||||||
|
// IdentRuntime has the same semantics as [IdentInputs].
|
||||||
|
IdentRuntime = unique.Make(Ident("runtime"))
|
||||||
|
|
||||||
// ErrInvalidInputs is panicked for an [IdentInputs] argument to [PF]
|
// ErrInvalidInputs is panicked for an [IdentInputs] argument to [PF]
|
||||||
// sharing its value or set for R.
|
// sharing its value or set for R.
|
||||||
@@ -274,6 +276,7 @@ func evaluateAny(d PF, s []Frame, expr, rp any) bool {
|
|||||||
fp.Val = maps.Clone(f.V)
|
fp.Val = maps.Clone(f.V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args:
|
||||||
for _, arg := range e.Args {
|
for _, arg := range e.Args {
|
||||||
names := make([]unique.Handle[Ident], len(arg.K))
|
names := make([]unique.Handle[Ident], len(arg.K))
|
||||||
for i, name := range arg.K {
|
for i, name := range arg.K {
|
||||||
@@ -281,16 +284,23 @@ func evaluateAny(d PF, s []Frame, expr, rp any) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
farg := FArg{R: arg.R}
|
farg := FArg{R: arg.R}
|
||||||
if e.Package && slices.Contains(names, IdentInputs) {
|
if e.Package {
|
||||||
if len(names) != 1 || len(arg.V) != 1 || arg.R {
|
for _, special := range [...]unique.Handle[Ident]{
|
||||||
panic(ErrInvalidInputs)
|
IdentInputs,
|
||||||
|
IdentRuntime,
|
||||||
|
} {
|
||||||
|
if slices.Contains(names, special) {
|
||||||
|
if len(names) != 1 || len(arg.V) != 1 || arg.R {
|
||||||
|
panic(ErrInvalidInputs)
|
||||||
|
}
|
||||||
|
farg.K = names[0]
|
||||||
|
if err := storeE(&farg.V, arg.V[0]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fargs = append(fargs, farg)
|
||||||
|
continue args
|
||||||
|
}
|
||||||
}
|
}
|
||||||
farg.K = names[0]
|
|
||||||
if err := storeE(&farg.V, arg.V[0]); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fargs = append(fargs, farg)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !evaluateAny(d, s, arg.V, &farg.V) {
|
if !evaluateAny(d, s, arg.V, &farg.V) {
|
||||||
@@ -333,7 +343,7 @@ func evaluateAny(d PF, s []Frame, expr, rp any) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate evaluates a statement and returns its value.
|
// Evaluate evaluates a statement and returns its value.
|
||||||
func Evaluate[T Value](d PF, s []Frame, expr any) (v T, set bool, err error) {
|
func Evaluate[T any](d PF, s []Frame, expr any) (v T, set bool, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
r := recover()
|
r := recover()
|
||||||
if r == nil {
|
if r == nil {
|
||||||
@@ -346,6 +356,6 @@ func Evaluate[T Value](d PF, s []Frame, expr any) (v T, set bool, err error) {
|
|||||||
}
|
}
|
||||||
err = _err
|
err = _err
|
||||||
}()
|
}()
|
||||||
set = evaluate[T](d, s, expr, &v)
|
set = evaluateAny(d, s, expr, &v)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ func TestEvaluate(t *testing.T) {
|
|||||||
Err: ErrInvalidInputs,
|
Err: ErrInvalidInputs,
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
{"bound runtime", `package name { runtime* = []; }`, nil, "", EvaluationError{
|
||||||
|
Expr: Func{
|
||||||
|
Ident: Ident("name"),
|
||||||
|
Package: true,
|
||||||
|
|
||||||
|
Args: []Arg{
|
||||||
|
{K: []Ident{"runtime"}, V: Val{Array(nil)}, R: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err: ErrInvalidInputs,
|
||||||
|
}},
|
||||||
|
|
||||||
{"concat inputs", `package name { inputs = ""+""; }`, nil, "", EvaluationError{
|
{"concat inputs", `package name { inputs = ""+""; }`, nil, "", EvaluationError{
|
||||||
Expr: Func{
|
Expr: Func{
|
||||||
Ident: Ident("name"),
|
Ident: Ident("name"),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func mustDecode(s string) pkg.Checksum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KV is a key-value pair of strings.
|
// KV is a key-value pair of strings.
|
||||||
type KV [2]string
|
type KV = [2]string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// AbsUsrSrc is the conventional directory to place source code under.
|
// AbsUsrSrc is the conventional directory to place source code under.
|
||||||
@@ -601,6 +601,9 @@ func newFromGitHubRelease(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// native contains natively-implemented and built-in azalea-based [Artifact].
|
||||||
|
// It is generally recommended to clone this instance for custom [Artifact]
|
||||||
|
// registrations.
|
||||||
var native S
|
var native S
|
||||||
|
|
||||||
// Native returns the global [S].
|
// Native returns the global [S].
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -12,8 +15,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unique"
|
"unique"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa/azalea"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArtifactH is a handle of the unique name of a prepared [pkg.Artifact].
|
// ArtifactH is a handle of the unique name of a prepared [pkg.Artifact].
|
||||||
@@ -179,6 +184,11 @@ type S struct {
|
|||||||
// For initialising arch.
|
// For initialising arch.
|
||||||
archOnce sync.Once
|
archOnce sync.Once
|
||||||
|
|
||||||
|
// Built-in functions.
|
||||||
|
s []azalea.Frame
|
||||||
|
// For initialising s.
|
||||||
|
sOnce sync.Once
|
||||||
|
|
||||||
// Options for [pkg.Artifact] created against [S].
|
// Options for [pkg.Artifact] created against [S].
|
||||||
opts int
|
opts int
|
||||||
|
|
||||||
@@ -300,10 +310,18 @@ func (s *S) Register(meta *Artifact) bool {
|
|||||||
return !ok
|
return !ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterError is returned or panicked when attempting to register multiple
|
||||||
|
// [Artifact] with the same name.
|
||||||
|
type RegisterError ArtifactH
|
||||||
|
|
||||||
|
func (e RegisterError) Error() string {
|
||||||
|
return "attempting to register " + strconv.Quote(ArtifactH(e).String()) + " twice"
|
||||||
|
}
|
||||||
|
|
||||||
// MustRegister is like Register, but panics if registration fails.
|
// MustRegister is like Register, but panics if registration fails.
|
||||||
func (s *S) MustRegister(meta *Artifact) {
|
func (s *S) MustRegister(meta *Artifact) {
|
||||||
if !s.Register(meta) {
|
if !s.Register(meta) {
|
||||||
panic("attempting to register " + strconv.Quote(meta.Name) + " twice")
|
panic(RegisterError(H(meta.Name)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,8 +331,8 @@ func (s *S) Count() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect returns all [ArtifactH] registered to s.
|
// Collect returns all [ArtifactH] registered to s.
|
||||||
func (s *S) Collect() (handles []ArtifactH) {
|
func (s *S) Collect() (handles P) {
|
||||||
handles = make([]ArtifactH, 0, s.Count())
|
handles = make(P, 0, s.Count())
|
||||||
s.artifacts.Range(func(key, _ any) bool {
|
s.artifacts.Range(func(key, _ any) bool {
|
||||||
handles = append(handles, key.(ArtifactH))
|
handles = append(handles, key.(ArtifactH))
|
||||||
return true
|
return true
|
||||||
@@ -325,6 +343,215 @@ func (s *S) Collect() (handles []ArtifactH) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getS must be called before accessing s. This value is not currently safe for
|
||||||
|
// concurrent use, but the underlying frame is immutable.
|
||||||
|
func (s *S) getS() []azalea.Frame {
|
||||||
|
s.sOnce.Do(func() {
|
||||||
|
s.wantsArch()
|
||||||
|
k := func(name string) unique.Handle[azalea.Ident] {
|
||||||
|
return unique.Make(azalea.Ident(name))
|
||||||
|
}
|
||||||
|
s.s = make([]azalea.Frame, 1, 1<<4)
|
||||||
|
s.s[0].Func = map[unique.Handle[azalea.Ident]]azalea.F{
|
||||||
|
|
||||||
|
// intenral/pkg built-ins
|
||||||
|
|
||||||
|
unique.Make(azalea.Ident("remoteTar")): {F: func(
|
||||||
|
args azalea.FArgs,
|
||||||
|
) (v any, set bool, err error) {
|
||||||
|
var url, checksum string
|
||||||
|
var compress uint32
|
||||||
|
if err = args.Apply(map[unique.Handle[azalea.Ident]]any{
|
||||||
|
k("url"): &url,
|
||||||
|
k("checksum"): &checksum,
|
||||||
|
k("compress"): &compress,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = newTar(url, checksum, compress)
|
||||||
|
set = true
|
||||||
|
return
|
||||||
|
}, V: map[unique.Handle[azalea.Ident]]any{
|
||||||
|
k("uncompressed"): uint32(pkg.TarUncompressed),
|
||||||
|
k("gzip"): uint32(pkg.TarGzip),
|
||||||
|
k("bzip2"): uint32(pkg.TarBzip2),
|
||||||
|
}},
|
||||||
|
|
||||||
|
// high-level helpers
|
||||||
|
|
||||||
|
unique.Make(azalea.Ident("make")): {F: func(
|
||||||
|
args azalea.FArgs,
|
||||||
|
) (v any, set bool, err error) {
|
||||||
|
var attr MakeHelper
|
||||||
|
if err = args.Apply(map[unique.Handle[azalea.Ident]]any{
|
||||||
|
k("omitDefaults"): &attr.OmitDefaults,
|
||||||
|
k("generate"): &attr.Generate,
|
||||||
|
k("preMake"): &attr.ScriptMakeEarly,
|
||||||
|
k("preCheck"): &attr.ScriptCheckEarly,
|
||||||
|
k("postInstall"): &attr.Script,
|
||||||
|
k("inPlace"): &attr.InPlace,
|
||||||
|
k("skipConfigure"): &attr.SkipConfigure,
|
||||||
|
k("configureName"): &attr.ConfigureName,
|
||||||
|
k("configure"): &attr.Configure,
|
||||||
|
k("host"): &attr.Host,
|
||||||
|
k("build"): &attr.Build,
|
||||||
|
k("make"): &attr.Make,
|
||||||
|
k("skipCheck"): &attr.SkipCheck,
|
||||||
|
k("check"): &attr.Check,
|
||||||
|
k("install"): &attr.Install,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = &attr
|
||||||
|
set = true
|
||||||
|
return
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return s.s
|
||||||
|
}
|
||||||
|
|
||||||
|
// toHandles makes handles out of an [azalea.Array] of identifiers.
|
||||||
|
func toHandles(idents azalea.Array) (P, error) {
|
||||||
|
handles := make(P, len(idents))
|
||||||
|
for i, p := range idents {
|
||||||
|
if len(p) != 1 {
|
||||||
|
return nil, azalea.EvaluationError{
|
||||||
|
Expr: p,
|
||||||
|
Err: errors.New("concatenation not allowed for handles"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s, ok := p[0].(azalea.Ident)
|
||||||
|
if !ok {
|
||||||
|
return nil, azalea.EvaluationError{
|
||||||
|
Expr: p[0],
|
||||||
|
Err: errors.New("identifiers expected for handles"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handles[i] = H(string(s))
|
||||||
|
}
|
||||||
|
return handles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalContext holds per-reader context.
|
||||||
|
type evalContext struct{ b fs.FS }
|
||||||
|
|
||||||
|
// f implements [azalea.PF].
|
||||||
|
func (ctx *evalContext) f(
|
||||||
|
name azalea.Ident,
|
||||||
|
args azalea.FArgs,
|
||||||
|
) (v any, set bool, err error) {
|
||||||
|
k := func(name string) unique.Handle[azalea.Ident] {
|
||||||
|
return unique.Make(azalea.Ident(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := Artifact{Name: string(name)}
|
||||||
|
var (
|
||||||
|
attr PackageAttr
|
||||||
|
patches []string
|
||||||
|
|
||||||
|
anitya int64
|
||||||
|
version string
|
||||||
|
source pkg.Artifact
|
||||||
|
helper Helper
|
||||||
|
|
||||||
|
inputs, runtimes azalea.Array
|
||||||
|
)
|
||||||
|
if err = args.Apply(map[unique.Handle[azalea.Ident]]any{
|
||||||
|
k("description"): &meta.Description,
|
||||||
|
k("website"): &meta.Website,
|
||||||
|
k("anitya"): &anitya,
|
||||||
|
|
||||||
|
k("version"): &version,
|
||||||
|
k("source"): &source,
|
||||||
|
|
||||||
|
k("writable"): &attr.Writable,
|
||||||
|
k("chmod"): &attr.Chmod,
|
||||||
|
k("enterSource"): &attr.EnterSource,
|
||||||
|
k("env"): &attr.Env,
|
||||||
|
k("early"): &attr.ScriptEarly,
|
||||||
|
k("patches"): &patches,
|
||||||
|
|
||||||
|
k("exec"): &helper,
|
||||||
|
k("inputs"): &inputs,
|
||||||
|
k("runtime"): &runtimes,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var inputsH P
|
||||||
|
if inputsH, err = toHandles(inputs); err != nil {
|
||||||
|
return
|
||||||
|
} else if meta.Dependencies, err = toHandles(runtimes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pathname := range patches {
|
||||||
|
var p []byte
|
||||||
|
p, err = fs.ReadFile(ctx.b, pathname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
attr.Patches = append(attr.Patches, KV{
|
||||||
|
strings.TrimSuffix(filepath.Base(pathname), ".patch"),
|
||||||
|
unsafe.String(unsafe.SliceData(p), len(p)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.ID = int(anitya)
|
||||||
|
meta.f = func(t Toolchain) (pkg.Artifact, string) {
|
||||||
|
return t.NewPackage(
|
||||||
|
meta.Name,
|
||||||
|
version,
|
||||||
|
source,
|
||||||
|
&attr,
|
||||||
|
helper,
|
||||||
|
inputsH...,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
|
||||||
|
v = meta
|
||||||
|
set = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrToplevel is returned by [S.Evaluate] when encountering a toplevel
|
||||||
|
// expression other than a package declaration.
|
||||||
|
ErrToplevel = errors.New("top level must only contain package declarations")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Evaluate defines all package declarations from r. The backing filesystem is
|
||||||
|
// directly exposed to azalea pathnames.
|
||||||
|
func (s *S) Evaluate(r io.Reader, b fs.FS) error {
|
||||||
|
var pending []*azalea.Func
|
||||||
|
if expressions, err := azalea.Parse(r); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
pending = make([]*azalea.Func, len(expressions))
|
||||||
|
for i, expr := range expressions {
|
||||||
|
f, ok := expr.(azalea.Func)
|
||||||
|
if !ok || !f.Package {
|
||||||
|
return ErrToplevel
|
||||||
|
}
|
||||||
|
pending[i] = &f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := evalContext{b}
|
||||||
|
for _, f := range pending {
|
||||||
|
meta, 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) {
|
||||||
|
return RegisterError(H(string(f.Ident)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetGentooStage3 sets the Gentoo stage3 tarball url and checksum. It panics
|
// SetGentooStage3 sets the Gentoo stage3 tarball url and checksum. It panics
|
||||||
// if given zero values or if these values have already been set.
|
// if given zero values or if these values have already been set.
|
||||||
func (s *S) SetGentooStage3(url string, checksum pkg.Checksum) {
|
func (s *S) SetGentooStage3(url string, checksum pkg.Checksum) {
|
||||||
|
|||||||
Reference in New Issue
Block a user