Compare commits
35 Commits
staging
...
0183064f25
| Author | SHA1 | Date | |
|---|---|---|---|
|
0183064f25
|
|||
| 0db8da9cba | |||
| 4ae5468cf6 | |||
| 226b86fe38 | |||
| 23b5d0dd3e | |||
| ba06769c5f | |||
| 28e84f0445 | |||
| 51ae9496e9 | |||
| 3cd8abc451 | |||
| edf4d0d32a | |||
| 97657ad9fc | |||
| 81344a1c8a | |||
| 50514edc00 | |||
| 7affd5735a | |||
| 0a2d9dbe42 | |||
| 049f212a02 | |||
| ccecaee7ca | |||
| 365602c9b6 | |||
| 85e066ccc4 | |||
| ffe37ae439 | |||
| 222a2aa8b4 | |||
| 29384553bf | |||
| cb0c652b18 | |||
| 5552598bc4 | |||
| 3db5603e78 | |||
| 08d120a84d | |||
| 1ecdcdc243 | |||
| e425c3b769 | |||
| 3d8b89e1ab | |||
| b2777de621 | |||
| 529a641fcd | |||
| 0e34ec3093 | |||
| 31b2d5431c | |||
| d6954e6bdb | |||
| 1cda0d83c3 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,6 +7,10 @@
|
||||
|
||||
# go generate
|
||||
/cmd/hakurei/LICENSE
|
||||
/cmd/pkgserver/.sass-cache
|
||||
/cmd/pkgserver/ui/static/*.js
|
||||
/cmd/pkgserver/ui/static/*.css*
|
||||
/cmd/pkgserver/ui/static/*.css.map
|
||||
/internal/pkg/testdata/testtool
|
||||
/internal/rosa/hakurei_current.tar.gz
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
@@ -30,16 +30,6 @@ func (e AbsoluteError) Is(target error) bool {
|
||||
// Absolute holds a pathname checked to be absolute.
|
||||
type Absolute struct{ pathname unique.Handle[string] }
|
||||
|
||||
var (
|
||||
_ encoding.TextAppender = new(Absolute)
|
||||
_ encoding.TextMarshaler = new(Absolute)
|
||||
_ encoding.TextUnmarshaler = new(Absolute)
|
||||
|
||||
_ encoding.BinaryAppender = new(Absolute)
|
||||
_ encoding.BinaryMarshaler = new(Absolute)
|
||||
_ encoding.BinaryUnmarshaler = new(Absolute)
|
||||
)
|
||||
|
||||
// ok returns whether [Absolute] is not the zero value.
|
||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
||||
|
||||
@@ -94,16 +84,13 @@ func (a *Absolute) Append(elem ...string) *Absolute {
|
||||
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
||||
|
||||
// AppendText appends the checked pathname.
|
||||
func (a *Absolute) AppendText(data []byte) ([]byte, error) {
|
||||
return append(data, a.String()...), nil
|
||||
// GobEncode returns the checked pathname.
|
||||
func (a *Absolute) GobEncode() ([]byte, error) {
|
||||
return []byte(a.String()), nil
|
||||
}
|
||||
|
||||
// MarshalText returns the checked pathname.
|
||||
func (a *Absolute) MarshalText() ([]byte, error) { return a.AppendText(nil) }
|
||||
|
||||
// UnmarshalText stores data if it represents an absolute pathname.
|
||||
func (a *Absolute) UnmarshalText(data []byte) error {
|
||||
// GobDecode stores data if it represents an absolute pathname.
|
||||
func (a *Absolute) GobDecode(data []byte) error {
|
||||
pathname := string(data)
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
@@ -112,9 +99,23 @@ func (a *Absolute) UnmarshalText(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Absolute) AppendBinary(data []byte) ([]byte, error) { return a.AppendText(data) }
|
||||
func (a *Absolute) MarshalBinary() ([]byte, error) { return a.MarshalText() }
|
||||
func (a *Absolute) UnmarshalBinary(data []byte) error { return a.UnmarshalText(data) }
|
||||
// MarshalJSON returns a JSON representation of the checked pathname.
|
||||
func (a *Absolute) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(a.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON stores data if it represents an absolute pathname.
|
||||
func (a *Absolute) UnmarshalJSON(data []byte) error {
|
||||
var pathname string
|
||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||
return err
|
||||
}
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
}
|
||||
a.pathname = unique.Make(pathname)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
||||
func SortAbs(x []*Absolute) {
|
||||
|
||||
@@ -170,20 +170,20 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
|
||||
{"good", MustAbs("/etc"),
|
||||
nil,
|
||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
||||
|
||||
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
||||
{"not absolute", nil,
|
||||
AbsoluteError("etc"),
|
||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||
|
||||
`"etc"`, `{"val":"etc","magic":3236757504}`},
|
||||
{"zero", nil,
|
||||
new(AbsoluteError),
|
||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||
`""`, `{"val":"","magic":3236757504}`},
|
||||
}
|
||||
|
||||
@@ -347,6 +347,15 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantErr := "invalid character ':' looking for beginning of value"
|
||||
if err := new(Absolute).UnmarshalJSON([]byte(":3")); err == nil || err.Error() != wantErr {
|
||||
t.Errorf("UnmarshalJSON: error = %v, want %s", err, wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAbsoluteWrap(t *testing.T) {
|
||||
|
||||
@@ -38,9 +38,8 @@ var errSuccess = errors.New("success")
|
||||
|
||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||
var (
|
||||
flagVerbose bool
|
||||
flagInsecure bool
|
||||
flagJSON bool
|
||||
flagVerbose bool
|
||||
flagJSON bool
|
||||
)
|
||||
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
||||
msg.SwapVerbose(flagVerbose)
|
||||
@@ -58,7 +57,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
return nil
|
||||
}).
|
||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||
Flag(&flagInsecure, "insecure", command.BoolFlag(false), "Allow use of insecure compatibility options").
|
||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||
|
||||
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
||||
@@ -77,12 +75,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
config.Container.Args = append(config.Container.Args, args[1:]...)
|
||||
}
|
||||
|
||||
var flags int
|
||||
if flagInsecure {
|
||||
flags |= hst.VAllowInsecure
|
||||
}
|
||||
|
||||
outcome.Main(ctx, msg, config, flags, flagIdentifierFile)
|
||||
outcome.Main(ctx, msg, config, flagIdentifierFile)
|
||||
panic("unreachable")
|
||||
}).
|
||||
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
|
||||
@@ -152,7 +145,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
}
|
||||
}
|
||||
|
||||
var et hst.Enablements
|
||||
var et hst.Enablement
|
||||
if flagWayland {
|
||||
et |= hst.EWayland
|
||||
}
|
||||
@@ -170,7 +163,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
ID: flagID,
|
||||
Identity: flagIdentity,
|
||||
Groups: flagGroups,
|
||||
Enablements: &et,
|
||||
Enablements: hst.NewEnablements(et),
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
@@ -289,7 +282,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Main(ctx, msg, &config, 0, -1)
|
||||
outcome.Main(ctx, msg, &config, -1)
|
||||
panic("unreachable")
|
||||
}).
|
||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestHelp(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"main", []string{}, `
|
||||
Usage: hakurei [-h | --help] [-v] [--insecure] [--json] COMMAND [OPTIONS]
|
||||
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
||||
|
||||
Commands:
|
||||
run Load and start container from configuration file
|
||||
|
||||
@@ -56,7 +56,7 @@ func printShowInstance(
|
||||
t := newPrinter(output)
|
||||
defer t.MustFlush()
|
||||
|
||||
if err := config.Validate(hst.VAllowInsecure); err != nil {
|
||||
if err := config.Validate(); err != nil {
|
||||
valid = false
|
||||
if m, ok := message.GetMessage(err); ok {
|
||||
mustPrint(output, "Error: "+m+"!\n\n")
|
||||
|
||||
@@ -32,7 +32,7 @@ var (
|
||||
PID: 0xbeef,
|
||||
ShimPID: 0xcafe,
|
||||
Config: &hst.Config{
|
||||
Enablements: new(hst.EWayland | hst.EPipeWire),
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire),
|
||||
Identity: 1,
|
||||
Container: &hst.ContainerConfig{
|
||||
Shell: check.MustAbs("/bin/sh"),
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// cache refers to an instance of [pkg.Cache] that might be open.
|
||||
type cache struct {
|
||||
ctx context.Context
|
||||
msg message.Msg
|
||||
|
||||
// Should generally not be used directly.
|
||||
c *pkg.Cache
|
||||
|
||||
cures, jobs int
|
||||
hostAbstract, idle bool
|
||||
|
||||
base string
|
||||
}
|
||||
|
||||
// open opens the underlying [pkg.Cache].
|
||||
func (cache *cache) open() (err error) {
|
||||
if cache.c != nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
if cache.base == "" {
|
||||
cache.base = "cache"
|
||||
}
|
||||
var base *check.Absolute
|
||||
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
||||
return
|
||||
} else if base, err = check.NewAbs(cache.base); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var flags int
|
||||
if cache.idle {
|
||||
flags |= pkg.CSchedIdle
|
||||
}
|
||||
if cache.hostAbstract {
|
||||
flags |= pkg.CHostAbstract
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
select {
|
||||
case <-cache.ctx.Done():
|
||||
if testing.Testing() {
|
||||
return
|
||||
}
|
||||
os.Exit(2)
|
||||
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
cache.msg.Verbosef("opening cache at %s", base)
|
||||
cache.c, err = pkg.Open(
|
||||
cache.ctx,
|
||||
cache.msg,
|
||||
flags,
|
||||
cache.cures,
|
||||
cache.jobs,
|
||||
base,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the underlying [pkg.Cache] if it is open.
|
||||
func (cache *cache) Close() {
|
||||
if cache.c != nil {
|
||||
cache.c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Do calls f on the underlying cache and returns its error value.
|
||||
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
|
||||
if cache.c == nil {
|
||||
if err := cache.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return f(cache.c)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cm := cache{
|
||||
ctx: t.Context(),
|
||||
msg: message.New(log.New(os.Stderr, "check: ", 0)),
|
||||
base: t.TempDir(),
|
||||
|
||||
hostAbstract: true, idle: true,
|
||||
}
|
||||
defer cm.Close()
|
||||
cm.Close()
|
||||
|
||||
if err := cm.open(); err != nil {
|
||||
t.Fatalf("open: error = %v", err)
|
||||
}
|
||||
if err := cm.open(); err != os.ErrInvalid {
|
||||
t.Errorf("(duplicate) open: error = %v", err)
|
||||
}
|
||||
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
return cache.Scrub(0)
|
||||
}); err != nil {
|
||||
t.Errorf("Scrub: error = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
"unique"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
// daemonTimeout is the maximum amount of time cureFromIR will wait on I/O.
|
||||
const daemonTimeout = 30 * time.Second
|
||||
|
||||
// daemonDeadline returns the deadline corresponding to daemonTimeout, or the
|
||||
// zero value when running in a test.
|
||||
func daemonDeadline() time.Time {
|
||||
if testing.Testing() {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Now().Add(daemonTimeout)
|
||||
}
|
||||
|
||||
const (
|
||||
// remoteNoReply notifies that the client will not receive a cure reply.
|
||||
remoteNoReply = 1 << iota
|
||||
)
|
||||
|
||||
// cureFromIR services an IR curing request.
|
||||
func cureFromIR(
|
||||
cache *pkg.Cache,
|
||||
conn net.Conn,
|
||||
flags uint64,
|
||||
) (pkg.Artifact, error) {
|
||||
a, decodeErr := cache.NewDecoder(conn).Decode()
|
||||
if decodeErr != nil {
|
||||
_, err := conn.Write([]byte("\x00" + decodeErr.Error()))
|
||||
return nil, errors.Join(decodeErr, err, conn.Close())
|
||||
}
|
||||
|
||||
pathname, _, cureErr := cache.Cure(a)
|
||||
if flags&remoteNoReply != 0 {
|
||||
return a, errors.Join(cureErr, conn.Close())
|
||||
}
|
||||
if err := conn.SetWriteDeadline(daemonDeadline()); err != nil {
|
||||
return a, errors.Join(cureErr, err, conn.Close())
|
||||
}
|
||||
if cureErr != nil {
|
||||
_, err := conn.Write([]byte("\x00" + cureErr.Error()))
|
||||
return a, errors.Join(cureErr, err, conn.Close())
|
||||
}
|
||||
_, err := conn.Write([]byte(pathname.String()))
|
||||
if testing.Testing() && errors.Is(err, io.ErrClosedPipe) {
|
||||
return a, nil
|
||||
}
|
||||
return a, errors.Join(err, conn.Close())
|
||||
}
|
||||
|
||||
const (
|
||||
// specialCancel is a message consisting of a single identifier referring
|
||||
// to a curing artifact to be cancelled.
|
||||
specialCancel = iota
|
||||
// specialAbort requests for all pending cures to be aborted. It has no
|
||||
// message body.
|
||||
specialAbort
|
||||
|
||||
// remoteSpecial denotes a special message with custom layout.
|
||||
remoteSpecial = math.MaxUint64
|
||||
)
|
||||
|
||||
// writeSpecialHeader writes the header of a remoteSpecial message.
|
||||
func writeSpecialHeader(conn net.Conn, kind uint64) error {
|
||||
var sh [16]byte
|
||||
binary.LittleEndian.PutUint64(sh[:], remoteSpecial)
|
||||
binary.LittleEndian.PutUint64(sh[8:], kind)
|
||||
if n, err := conn.Write(sh[:]); err != nil {
|
||||
return err
|
||||
} else if n != len(sh) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cancelIdent reads an identifier from conn and cancels the corresponding cure.
|
||||
func cancelIdent(
|
||||
cache *pkg.Cache,
|
||||
conn net.Conn,
|
||||
) (*pkg.ID, bool, error) {
|
||||
var ident pkg.ID
|
||||
if _, err := io.ReadFull(conn, ident[:]); err != nil {
|
||||
return nil, false, errors.Join(err, conn.Close())
|
||||
} else if err = conn.Close(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &ident, cache.Cancel(unique.Make(ident)), nil
|
||||
}
|
||||
|
||||
// serve services connections from a [net.UnixListener].
|
||||
func serve(
|
||||
ctx context.Context,
|
||||
log *log.Logger,
|
||||
cm *cache,
|
||||
ul *net.UnixListener,
|
||||
) error {
|
||||
ul.SetUnlinkOnClose(true)
|
||||
if cm.c == nil {
|
||||
if err := cm.open(); err != nil {
|
||||
return errors.Join(err, ul.Close())
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
wg.Go(func() {
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
conn, err := ul.AcceptUnix()
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
log.Println(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
wg.Go(func() {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = conn.SetDeadline(time.Now())
|
||||
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if _err := conn.SetReadDeadline(daemonDeadline()); _err != nil {
|
||||
log.Println(_err)
|
||||
if _err = conn.Close(); _err != nil {
|
||||
log.Println(_err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var word [8]byte
|
||||
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
||||
log.Println(_err)
|
||||
if _err = conn.Close(); _err != nil {
|
||||
log.Println(_err)
|
||||
}
|
||||
return
|
||||
}
|
||||
flags := binary.LittleEndian.Uint64(word[:])
|
||||
|
||||
if flags == remoteSpecial {
|
||||
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
||||
log.Println(_err)
|
||||
if _err = conn.Close(); _err != nil {
|
||||
log.Println(_err)
|
||||
}
|
||||
return
|
||||
}
|
||||
switch special := binary.LittleEndian.Uint64(word[:]); special {
|
||||
default:
|
||||
log.Printf("invalid special %d", special)
|
||||
|
||||
case specialCancel:
|
||||
if id, ok, _err := cancelIdent(cm.c, conn); _err != nil {
|
||||
log.Println(_err)
|
||||
} else if !ok {
|
||||
log.Println(
|
||||
"attempting to cancel invalid artifact",
|
||||
pkg.Encode(*id),
|
||||
)
|
||||
} else {
|
||||
log.Println(
|
||||
"cancelled artifact",
|
||||
pkg.Encode(*id),
|
||||
)
|
||||
}
|
||||
|
||||
case specialAbort:
|
||||
if _err := conn.Close(); _err != nil {
|
||||
log.Println(_err)
|
||||
}
|
||||
log.Println("aborting all pending cures")
|
||||
cm.c.Abort()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if a, _err := cureFromIR(cm.c, conn, flags); _err != nil {
|
||||
log.Println(_err)
|
||||
} else {
|
||||
log.Printf(
|
||||
"fulfilled artifact %s",
|
||||
pkg.Encode(cm.c.Ident(a).Value()),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
<-ctx.Done()
|
||||
if err := ul.SetDeadline(time.Now()); err != nil {
|
||||
return errors.Join(err, ul.Close())
|
||||
}
|
||||
wg.Wait()
|
||||
return ul.Close()
|
||||
}
|
||||
|
||||
// dial wraps [net.DialUnix] with a context.
|
||||
func dial(ctx context.Context, addr *net.UnixAddr) (
|
||||
done chan<- struct{},
|
||||
conn *net.UnixConn,
|
||||
err error,
|
||||
) {
|
||||
conn, err = net.DialUnix("unix", nil, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
d := make(chan struct{})
|
||||
done = d
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = conn.SetDeadline(time.Now())
|
||||
|
||||
case <-d:
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
// cureRemote cures a [pkg.Artifact] on a daemon.
|
||||
func cureRemote(
|
||||
ctx context.Context,
|
||||
addr *net.UnixAddr,
|
||||
a pkg.Artifact,
|
||||
flags uint64,
|
||||
) (*check.Absolute, error) {
|
||||
if flags == remoteSpecial {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
done, conn, err := dial(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer close(done)
|
||||
|
||||
if n, flagErr := conn.Write(binary.LittleEndian.AppendUint64(nil, flags)); flagErr != nil {
|
||||
return nil, errors.Join(flagErr, conn.Close())
|
||||
} else if n != 8 {
|
||||
return nil, errors.Join(io.ErrShortWrite, conn.Close())
|
||||
}
|
||||
|
||||
if err = pkg.NewIR().EncodeAll(conn, a); err != nil {
|
||||
return nil, errors.Join(err, conn.Close())
|
||||
} else if err = conn.CloseWrite(); err != nil {
|
||||
return nil, errors.Join(err, conn.Close())
|
||||
}
|
||||
|
||||
if flags&remoteNoReply != 0 {
|
||||
return nil, conn.Close()
|
||||
}
|
||||
|
||||
payload, recvErr := io.ReadAll(conn)
|
||||
if err = errors.Join(recvErr, conn.Close()); err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
if cancelErr := ctx.Err(); cancelErr != nil {
|
||||
err = cancelErr
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(payload) > 0 && payload[0] == 0 {
|
||||
return nil, errors.New(string(payload[1:]))
|
||||
}
|
||||
|
||||
var p *check.Absolute
|
||||
p, err = check.NewAbs(string(payload))
|
||||
return p, err
|
||||
}
|
||||
|
||||
// cancelRemote cancels a [pkg.Artifact] curing on a daemon.
|
||||
func cancelRemote(
|
||||
ctx context.Context,
|
||||
addr *net.UnixAddr,
|
||||
a pkg.Artifact,
|
||||
) error {
|
||||
done, conn, err := dial(ctx, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close(done)
|
||||
|
||||
if err = writeSpecialHeader(conn, specialCancel); err != nil {
|
||||
return errors.Join(err, conn.Close())
|
||||
}
|
||||
|
||||
var n int
|
||||
id := pkg.NewIR().Ident(a).Value()
|
||||
if n, err = conn.Write(id[:]); err != nil {
|
||||
return errors.Join(err, conn.Close())
|
||||
} else if n != len(id) {
|
||||
return errors.Join(io.ErrShortWrite, conn.Close())
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
|
||||
func abortRemote(
|
||||
ctx context.Context,
|
||||
addr *net.UnixAddr,
|
||||
) error {
|
||||
done, conn, err := dial(ctx, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close(done)
|
||||
|
||||
err = writeSpecialHeader(conn, specialAbort)
|
||||
return errors.Join(err, conn.Close())
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestNoReply(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !daemonDeadline().IsZero() {
|
||||
t.Fatal("daemonDeadline did not return the zero value")
|
||||
}
|
||||
|
||||
c, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(log.New(os.Stderr, "cir: ", 0)),
|
||||
0, 0, 0,
|
||||
check.MustAbs(t.TempDir()),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Open: error = %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
client, server := net.Pipe()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
go func() {
|
||||
<-t.Context().Done()
|
||||
if _err := client.SetDeadline(time.Now()); _err != nil && !errors.Is(_err, io.ErrClosedPipe) {
|
||||
panic(_err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _err := c.EncodeAll(
|
||||
client,
|
||||
pkg.NewFile("check", []byte{0}),
|
||||
); _err != nil {
|
||||
panic(_err)
|
||||
} else if _err = client.Close(); _err != nil {
|
||||
panic(_err)
|
||||
}
|
||||
}()
|
||||
|
||||
a, cureErr := cureFromIR(c, server, remoteNoReply)
|
||||
if cureErr != nil {
|
||||
t.Fatalf("cureFromIR: error = %v", cureErr)
|
||||
}
|
||||
|
||||
<-done
|
||||
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
||||
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
||||
t.Errorf(
|
||||
"cureFromIR: %s, want %s",
|
||||
pkg.Encode(gotIdent), pkg.Encode(wantIdent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemon(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
logger := log.New(&buf, "daemon: ", 0)
|
||||
|
||||
addr := net.UnixAddr{
|
||||
Name: filepath.Join(t.TempDir(), "daemon"),
|
||||
Net: "unix",
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
cm := cache{
|
||||
ctx: ctx,
|
||||
msg: message.New(logger),
|
||||
base: t.TempDir(),
|
||||
}
|
||||
defer cm.Close()
|
||||
|
||||
ul, err := net.ListenUnix("unix", &addr)
|
||||
if err != nil {
|
||||
t.Fatalf("ListenUnix: error = %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
if _err := serve(ctx, logger, &cm, ul); _err != nil {
|
||||
panic(_err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil)); err != nil {
|
||||
t.Fatalf("cancelRemote: error = %v", err)
|
||||
}
|
||||
|
||||
if err = abortRemote(ctx, &addr); err != nil {
|
||||
t.Fatalf("abortRemote: error = %v", err)
|
||||
}
|
||||
|
||||
// keep this last for synchronisation
|
||||
var p *check.Absolute
|
||||
p, err = cureRemote(ctx, &addr, pkg.NewFile("check", []byte{0}), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("cureRemote: error = %v", err)
|
||||
}
|
||||
|
||||
cancel()
|
||||
<-done
|
||||
|
||||
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
||||
if got := filepath.Base(p.String()); got != want {
|
||||
t.Errorf("cureRemote: %s, want %s", got, want)
|
||||
}
|
||||
|
||||
wantLog := []string{
|
||||
"",
|
||||
"daemon: aborting all pending cures",
|
||||
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
||||
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
||||
}
|
||||
gotLog := strings.Split(buf.String(), "\n")
|
||||
slices.Sort(gotLog)
|
||||
if !slices.Equal(gotLog, wantLog) {
|
||||
t.Errorf(
|
||||
"serve: logged\n%s\nwant\n%s",
|
||||
strings.Join(gotLog, "\n"), strings.Join(wantLog, "\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
127
cmd/mbf/info.go
127
cmd/mbf/info.go
@@ -1,127 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
)
|
||||
|
||||
// commandInfo implements the info subcommand.
|
||||
func commandInfo(
|
||||
cm *cache,
|
||||
args []string,
|
||||
w io.Writer,
|
||||
writeStatus bool,
|
||||
reportPath string,
|
||||
) (err error) {
|
||||
if len(args) == 0 {
|
||||
return errors.New("info requires at least 1 argument")
|
||||
}
|
||||
|
||||
var r *rosa.Report
|
||||
if reportPath != "" {
|
||||
if r, err = rosa.OpenReport(reportPath); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := r.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
defer r.HandleAccess(&err)()
|
||||
}
|
||||
|
||||
// recovered by HandleAccess
|
||||
mustPrintln := func(a ...any) {
|
||||
if _, _err := fmt.Fprintln(w, a...); _err != nil {
|
||||
panic(_err)
|
||||
}
|
||||
}
|
||||
mustPrint := func(a ...any) {
|
||||
if _, _err := fmt.Fprint(w, a...); _err != nil {
|
||||
panic(_err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, name := range args {
|
||||
if p, ok := rosa.ResolveName(name); !ok {
|
||||
return fmt.Errorf("unknown artifact %q", name)
|
||||
} else {
|
||||
var suffix string
|
||||
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||
suffix += "-" + version
|
||||
}
|
||||
mustPrintln("name : " + name + suffix)
|
||||
|
||||
meta := rosa.GetMetadata(p)
|
||||
mustPrintln("description : " + meta.Description)
|
||||
if meta.Website != "" {
|
||||
mustPrintln("website : " +
|
||||
strings.TrimSuffix(meta.Website, "/"))
|
||||
}
|
||||
if len(meta.Dependencies) > 0 {
|
||||
mustPrint("depends on :")
|
||||
for _, d := range meta.Dependencies {
|
||||
s := rosa.GetMetadata(d).Name
|
||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||
s += "-" + version
|
||||
}
|
||||
mustPrint(" " + s)
|
||||
}
|
||||
mustPrintln()
|
||||
}
|
||||
|
||||
const statusPrefix = "status : "
|
||||
if writeStatus {
|
||||
if r == nil {
|
||||
var f io.ReadSeekCloser
|
||||
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
mustPrintln(
|
||||
statusPrefix + "not yet cured",
|
||||
)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
mustPrint(statusPrefix)
|
||||
_, err = io.Copy(w, f)
|
||||
if err = errors.Join(err, f.Close()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||
if status == nil {
|
||||
mustPrintln(
|
||||
statusPrefix + "not in report",
|
||||
)
|
||||
} else {
|
||||
mustPrintln("size :", n)
|
||||
mustPrint(statusPrefix)
|
||||
if _, err = w.Write(status); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if i != len(args)-1 {
|
||||
mustPrintln()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
status map[string]string
|
||||
report string
|
||||
want string
|
||||
wantErr any
|
||||
}{
|
||||
{"qemu", []string{"qemu"}, nil, "", `
|
||||
name : qemu-` + rosa.Std.Version(rosa.QEMU) + `
|
||||
description : a generic and open source machine emulator and virtualizer
|
||||
website : https://www.qemu.org
|
||||
depends on : glib-` + rosa.Std.Version(rosa.GLib) + ` zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||
`, nil},
|
||||
|
||||
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
||||
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
|
||||
description : low-level userspace tooling for Rosa OS
|
||||
website : https://hakurei.app
|
||||
|
||||
name : hakurei-dist-` + rosa.Std.Version(rosa.HakureiDist) + `
|
||||
description : low-level userspace tooling for Rosa OS (distribution tarball)
|
||||
website : https://hakurei.app
|
||||
`, nil},
|
||||
|
||||
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
|
||||
`, fmt.Errorf("unknown artifact %q", "\x00")},
|
||||
|
||||
{"status cache", []string{"zlib", "zstd"}, map[string]string{
|
||||
"zstd": "internal/pkg (amd64) on satori\n",
|
||||
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
||||
}, "", `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
status : not yet cured
|
||||
|
||||
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||
description : a fast compression algorithm
|
||||
website : https://facebook.github.io/zstd
|
||||
status : internal/pkg (amd64) on satori
|
||||
`, nil},
|
||||
|
||||
{"status cache perm", []string{"zlib"}, map[string]string{
|
||||
"zlib": "\x00",
|
||||
}, "", `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
`, func(cm *cache) error {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(rosa.Std.Load(rosa.Zlib)).Value())),
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
}},
|
||||
|
||||
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
status : not in report
|
||||
`, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
cm *cache
|
||||
buf strings.Builder
|
||||
rp string
|
||||
)
|
||||
|
||||
if tc.status != nil || tc.report != "" {
|
||||
cm = &cache{
|
||||
ctx: context.Background(),
|
||||
msg: message.New(log.New(os.Stderr, "info: ", 0)),
|
||||
base: t.TempDir(),
|
||||
}
|
||||
defer cm.Close()
|
||||
}
|
||||
|
||||
if tc.report != "" {
|
||||
rp = filepath.Join(t.TempDir(), "report")
|
||||
if err := os.WriteFile(
|
||||
rp,
|
||||
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
|
||||
0400,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.status != nil {
|
||||
for name, status := range tc.status {
|
||||
p, ok := rosa.ResolveName(name)
|
||||
if !ok {
|
||||
t.Fatalf("invalid name %q", name)
|
||||
}
|
||||
perm := os.FileMode(0400)
|
||||
if status == "\x00" {
|
||||
perm = 0
|
||||
}
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
return os.WriteFile(filepath.Join(
|
||||
cm.base,
|
||||
"status",
|
||||
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
|
||||
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
||||
}); err != nil {
|
||||
t.Fatalf("Do: error = %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wantErr error
|
||||
switch c := tc.wantErr.(type) {
|
||||
case error:
|
||||
wantErr = c
|
||||
case func(cm *cache) error:
|
||||
wantErr = c(cm)
|
||||
default:
|
||||
if tc.wantErr != nil {
|
||||
t.Fatalf("invalid wantErr %#v", tc.wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := commandInfo(
|
||||
cm,
|
||||
tc.args,
|
||||
&buf,
|
||||
cm != nil,
|
||||
rp,
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
|
||||
if got := buf.String(); got != strings.TrimPrefix(tc.want, "\n") {
|
||||
t.Errorf("commandInfo:\n%s\nwant\n%s", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
461
cmd/mbf/main.go
461
cmd/mbf/main.go
@@ -14,17 +14,16 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -54,13 +53,14 @@ func main() {
|
||||
log.Fatal("this program must not run as root")
|
||||
}
|
||||
|
||||
var cache *pkg.Cache
|
||||
ctx, stop := signal.NotifyContext(context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer stop()
|
||||
|
||||
var cm cache
|
||||
defer func() {
|
||||
cm.Close()
|
||||
if cache != nil {
|
||||
cache.Close()
|
||||
}
|
||||
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println(r)
|
||||
@@ -70,65 +70,60 @@ func main() {
|
||||
|
||||
var (
|
||||
flagQuiet bool
|
||||
flagCures int
|
||||
flagBase string
|
||||
flagIdle bool
|
||||
|
||||
addr net.UnixAddr
|
||||
flagHostAbstract bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
|
||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
||||
msg.SwapVerbose(!flagQuiet)
|
||||
cm.ctx, cm.msg = ctx, msg
|
||||
cm.base = os.ExpandEnv(cm.base)
|
||||
|
||||
addr.Net = "unix"
|
||||
addr.Name = os.ExpandEnv(addr.Name)
|
||||
if addr.Name == "" {
|
||||
addr.Name = "daemon"
|
||||
flagBase = os.ExpandEnv(flagBase)
|
||||
if flagBase == "" {
|
||||
flagBase = "cache"
|
||||
}
|
||||
|
||||
return nil
|
||||
var base *check.Absolute
|
||||
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
||||
return
|
||||
} else if base, err = check.NewAbs(flagBase); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var flags int
|
||||
if flagIdle {
|
||||
flags |= pkg.CSchedIdle
|
||||
}
|
||||
if flagHostAbstract {
|
||||
flags |= pkg.CHostAbstract
|
||||
}
|
||||
cache, err = pkg.Open(ctx, msg, flags, flagCures, base)
|
||||
|
||||
return
|
||||
}).Flag(
|
||||
&flagQuiet,
|
||||
"q", command.BoolFlag(false),
|
||||
"Do not print cure messages",
|
||||
).Flag(
|
||||
&cm.cures,
|
||||
&flagCures,
|
||||
"cures", command.IntFlag(0),
|
||||
"Maximum number of dependencies to cure at any given time",
|
||||
).Flag(
|
||||
&cm.jobs,
|
||||
"jobs", command.IntFlag(0),
|
||||
"Preferred number of jobs to run, when applicable",
|
||||
).Flag(
|
||||
&cm.base,
|
||||
&flagBase,
|
||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||
"Directory to store cured artifacts",
|
||||
).Flag(
|
||||
&cm.idle,
|
||||
&flagIdle,
|
||||
"sched-idle", command.BoolFlag(false),
|
||||
"Set SCHED_IDLE scheduling policy",
|
||||
).Flag(
|
||||
&cm.hostAbstract,
|
||||
&flagHostAbstract,
|
||||
"host-abstract", command.BoolFlag(
|
||||
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
||||
),
|
||||
"Do not restrict networked cure containers from connecting to host "+
|
||||
"abstract UNIX sockets",
|
||||
).Flag(
|
||||
&addr.Name,
|
||||
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
|
||||
"Pathname of socket to bind to",
|
||||
)
|
||||
|
||||
c.NewCommand(
|
||||
"checksum", "Compute checksum of data read from standard input",
|
||||
func([]string) error {
|
||||
go func() { <-ctx.Done(); os.Exit(1) }()
|
||||
h := sha512.New384()
|
||||
if _, err := io.Copy(h, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
{
|
||||
@@ -142,9 +137,7 @@ func main() {
|
||||
if flagShifts < 0 || flagShifts > 31 {
|
||||
flagShifts = 12
|
||||
}
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
||||
})
|
||||
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
||||
},
|
||||
).Flag(
|
||||
&flagShifts,
|
||||
@@ -162,17 +155,105 @@ func main() {
|
||||
"info",
|
||||
"Display out-of-band metadata of an artifact",
|
||||
func(args []string) (err error) {
|
||||
return commandInfo(&cm, args, os.Stdout, flagStatus, flagReport)
|
||||
if len(args) == 0 {
|
||||
return errors.New("info requires at least 1 argument")
|
||||
}
|
||||
|
||||
var r *rosa.Report
|
||||
if flagReport != "" {
|
||||
if r, err = rosa.OpenReport(flagReport); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := r.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
defer r.HandleAccess(&err)()
|
||||
}
|
||||
|
||||
for i, name := range args {
|
||||
if p, ok := rosa.ResolveName(name); !ok {
|
||||
return fmt.Errorf("unknown artifact %q", name)
|
||||
} else {
|
||||
var suffix string
|
||||
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||
suffix += "-" + version
|
||||
}
|
||||
fmt.Println("name : " + name + suffix)
|
||||
|
||||
meta := rosa.GetMetadata(p)
|
||||
fmt.Println("description : " + meta.Description)
|
||||
if meta.Website != "" {
|
||||
fmt.Println("website : " +
|
||||
strings.TrimSuffix(meta.Website, "/"))
|
||||
}
|
||||
if len(meta.Dependencies) > 0 {
|
||||
fmt.Print("depends on :")
|
||||
for _, d := range meta.Dependencies {
|
||||
s := rosa.GetMetadata(d).Name
|
||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||
s += "-" + version
|
||||
}
|
||||
fmt.Print(" " + s)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
const statusPrefix = "status : "
|
||||
if flagStatus {
|
||||
if r == nil {
|
||||
var f io.ReadSeekCloser
|
||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
fmt.Println(
|
||||
statusPrefix + "not yet cured",
|
||||
)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Print(statusPrefix)
|
||||
_, err = io.Copy(os.Stdout, f)
|
||||
if err = errors.Join(err, f.Close()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||
if status == nil {
|
||||
fmt.Println(
|
||||
statusPrefix + "not in report",
|
||||
)
|
||||
} else {
|
||||
fmt.Println("size :", n)
|
||||
fmt.Print(statusPrefix)
|
||||
if _, err = os.Stdout.Write(status); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i != len(args)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
).Flag(
|
||||
&flagStatus,
|
||||
"status", command.BoolFlag(false),
|
||||
"Display cure status if available",
|
||||
).Flag(
|
||||
&flagReport,
|
||||
"report", command.StringFlag(""),
|
||||
"Load cure status from this report file instead of cache",
|
||||
)
|
||||
).
|
||||
Flag(
|
||||
&flagStatus,
|
||||
"status", command.BoolFlag(false),
|
||||
"Display cure status if available",
|
||||
).
|
||||
Flag(
|
||||
&flagReport,
|
||||
"report", command.StringFlag(""),
|
||||
"Load cure status from this report file instead of cache",
|
||||
)
|
||||
}
|
||||
|
||||
c.NewCommand(
|
||||
@@ -206,9 +287,7 @@ func main() {
|
||||
if ext.Isatty(int(w.Fd())) {
|
||||
return errors.New("output appears to be a terminal")
|
||||
}
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
return rosa.WriteReport(msg, w, cache)
|
||||
})
|
||||
return rosa.WriteReport(msg, w, cache)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -271,26 +350,14 @@ func main() {
|
||||
" package(s) are out of date"))
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}).Flag(
|
||||
&flagJobs,
|
||||
"j", command.IntFlag(32),
|
||||
"Maximum number of simultaneous connections",
|
||||
)
|
||||
}).
|
||||
Flag(
|
||||
&flagJobs,
|
||||
"j", command.IntFlag(32),
|
||||
"Maximum number of simultaneous connections",
|
||||
)
|
||||
}
|
||||
|
||||
c.NewCommand(
|
||||
"daemon",
|
||||
"Service artifact IR with Rosa OS extensions",
|
||||
func(args []string) error {
|
||||
ul, err := net.ListenUnix("unix", &addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("listening on pathname socket at %s", addr.Name)
|
||||
return serve(ctx, log.Default(), &cm, ul)
|
||||
},
|
||||
)
|
||||
|
||||
{
|
||||
var (
|
||||
flagGentoo string
|
||||
@@ -315,37 +382,25 @@ func main() {
|
||||
rosa.SetGentooStage3(flagGentoo, checksum)
|
||||
}
|
||||
|
||||
_, _, _, stage1 := (t - 2).NewLLVM()
|
||||
_, _, _, stage2 := (t - 1).NewLLVM()
|
||||
_, _, _, stage3 := t.NewLLVM()
|
||||
var (
|
||||
pathname *check.Absolute
|
||||
checksum [2]unique.Handle[pkg.Checksum]
|
||||
)
|
||||
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, _, err = cache.Cure(
|
||||
(t - 2).Load(rosa.Clang),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
if pathname, _, err = cache.Cure(stage1); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("stage1:", pathname)
|
||||
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, checksum[0], err = cache.Cure(
|
||||
(t - 1).Load(rosa.Clang),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("stage2:", pathname)
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, checksum[1], err = cache.Cure(
|
||||
t.Load(rosa.Clang),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("stage3:", pathname)
|
||||
|
||||
@@ -362,41 +417,39 @@ func main() {
|
||||
}
|
||||
|
||||
if flagStage0 {
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, _, err = cache.Cure(
|
||||
t.Load(rosa.Stage0),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
if pathname, _, err = cache.Cure(
|
||||
t.Load(rosa.Stage0),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println(pathname)
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
).Flag(
|
||||
&flagGentoo,
|
||||
"gentoo", command.StringFlag(""),
|
||||
"Bootstrap from a Gentoo stage3 tarball",
|
||||
).Flag(
|
||||
&flagChecksum,
|
||||
"checksum", command.StringFlag(""),
|
||||
"Checksum of Gentoo stage3 tarball",
|
||||
).Flag(
|
||||
&flagStage0,
|
||||
"stage0", command.BoolFlag(false),
|
||||
"Create bootstrap stage0 tarball",
|
||||
)
|
||||
).
|
||||
Flag(
|
||||
&flagGentoo,
|
||||
"gentoo", command.StringFlag(""),
|
||||
"Bootstrap from a Gentoo stage3 tarball",
|
||||
).
|
||||
Flag(
|
||||
&flagChecksum,
|
||||
"checksum", command.StringFlag(""),
|
||||
"Checksum of Gentoo stage3 tarball",
|
||||
).
|
||||
Flag(
|
||||
&flagStage0,
|
||||
"stage0", command.BoolFlag(false),
|
||||
"Create bootstrap stage0 tarball",
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
var (
|
||||
flagDump string
|
||||
flagEnter bool
|
||||
flagExport string
|
||||
flagRemote bool
|
||||
flagNoReply bool
|
||||
flagDump string
|
||||
flagEnter bool
|
||||
flagExport string
|
||||
)
|
||||
c.NewCommand(
|
||||
"cure",
|
||||
@@ -412,11 +465,7 @@ func main() {
|
||||
|
||||
switch {
|
||||
default:
|
||||
var pathname *check.Absolute
|
||||
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, _, err = cache.Cure(rosa.Std.Load(p))
|
||||
return
|
||||
})
|
||||
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -456,7 +505,7 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pkg.NewIR().EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
||||
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
@@ -464,68 +513,33 @@ func main() {
|
||||
return f.Close()
|
||||
|
||||
case flagEnter:
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
return cache.EnterExec(
|
||||
ctx,
|
||||
rosa.Std.Load(p),
|
||||
true, os.Stdin, os.Stdout, os.Stderr,
|
||||
rosa.AbsSystem.Append("bin", "mksh"),
|
||||
"sh",
|
||||
)
|
||||
})
|
||||
|
||||
case flagRemote:
|
||||
var flags uint64
|
||||
if flagNoReply {
|
||||
flags |= remoteNoReply
|
||||
}
|
||||
a := rosa.Std.Load(p)
|
||||
pathname, err := cureRemote(ctx, &addr, a, flags)
|
||||
if !flagNoReply && err == nil {
|
||||
log.Println(pathname)
|
||||
}
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
cc, cancel := context.WithDeadline(context.Background(), daemonDeadline())
|
||||
defer cancel()
|
||||
|
||||
if _err := cancelRemote(cc, &addr, a); _err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return cache.EnterExec(
|
||||
ctx,
|
||||
rosa.Std.Load(p),
|
||||
true, os.Stdin, os.Stdout, os.Stderr,
|
||||
rosa.AbsSystem.Append("bin", "mksh"),
|
||||
"sh",
|
||||
)
|
||||
}
|
||||
},
|
||||
).Flag(
|
||||
&flagDump,
|
||||
"dump", command.StringFlag(""),
|
||||
"Write IR to specified pathname and terminate",
|
||||
).Flag(
|
||||
&flagExport,
|
||||
"export", command.StringFlag(""),
|
||||
"Export cured artifact to specified pathname",
|
||||
).Flag(
|
||||
&flagEnter,
|
||||
"enter", command.BoolFlag(false),
|
||||
"Enter cure container with an interactive shell",
|
||||
).Flag(
|
||||
&flagRemote,
|
||||
"daemon", command.BoolFlag(false),
|
||||
"Cure artifact on the daemon",
|
||||
).Flag(
|
||||
&flagNoReply,
|
||||
"no-reply", command.BoolFlag(false),
|
||||
"Do not receive a reply from the daemon",
|
||||
)
|
||||
).
|
||||
Flag(
|
||||
&flagDump,
|
||||
"dump", command.StringFlag(""),
|
||||
"Write IR to specified pathname and terminate",
|
||||
).
|
||||
Flag(
|
||||
&flagExport,
|
||||
"export", command.StringFlag(""),
|
||||
"Export cured artifact to specified pathname",
|
||||
).
|
||||
Flag(
|
||||
&flagEnter,
|
||||
"enter", command.BoolFlag(false),
|
||||
"Enter cure container with an interactive shell",
|
||||
)
|
||||
}
|
||||
|
||||
c.NewCommand(
|
||||
"abort",
|
||||
"Abort all pending cures on the daemon",
|
||||
func([]string) error { return abortRemote(ctx, &addr) },
|
||||
)
|
||||
|
||||
{
|
||||
var (
|
||||
flagNet bool
|
||||
@@ -537,7 +551,7 @@ func main() {
|
||||
"shell",
|
||||
"Interactive shell in the specified Rosa OS environment",
|
||||
func(args []string) error {
|
||||
presets := make([]rosa.PArtifact, len(args)+3)
|
||||
presets := make([]rosa.PArtifact, len(args))
|
||||
for i, arg := range args {
|
||||
p, ok := rosa.ResolveName(arg)
|
||||
if !ok {
|
||||
@@ -545,24 +559,21 @@ func main() {
|
||||
}
|
||||
presets[i] = p
|
||||
}
|
||||
|
||||
base := rosa.Clang
|
||||
if !flagWithToolchain {
|
||||
base = rosa.Musl
|
||||
}
|
||||
presets = append(presets,
|
||||
base,
|
||||
rosa.Mksh,
|
||||
rosa.Toybox,
|
||||
)
|
||||
|
||||
root := make(pkg.Collect, 0, 6+len(args))
|
||||
root = rosa.Std.AppendPresets(root, presets...)
|
||||
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
_, _, err := cache.Cure(&root)
|
||||
return err
|
||||
}); err == nil {
|
||||
if flagWithToolchain {
|
||||
musl, compilerRT, runtimes, clang := (rosa.Std - 1).NewLLVM()
|
||||
root = append(root, musl, compilerRT, runtimes, clang)
|
||||
} else {
|
||||
root = append(root, rosa.Std.Load(rosa.Musl))
|
||||
}
|
||||
root = append(root,
|
||||
rosa.Std.Load(rosa.Mksh),
|
||||
rosa.Std.Load(rosa.Toybox),
|
||||
)
|
||||
|
||||
if _, _, err := cache.Cure(&root); err == nil {
|
||||
return errors.New("unreachable")
|
||||
} else if !pkg.IsCollected(err) {
|
||||
return err
|
||||
@@ -574,22 +585,11 @@ func main() {
|
||||
}
|
||||
cured := make(map[pkg.Artifact]cureRes)
|
||||
for _, a := range root {
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
pathname, checksum, err := cache.Cure(a)
|
||||
if err == nil {
|
||||
cured[a] = cureRes{pathname, checksum}
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// explicitly open for direct error-free use from this point
|
||||
if cm.c == nil {
|
||||
if err := cm.open(); err != nil {
|
||||
pathname, checksum, err := cache.Cure(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cured[a] = cureRes{pathname, checksum}
|
||||
}
|
||||
|
||||
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
||||
@@ -599,7 +599,7 @@ func main() {
|
||||
res := cured[a]
|
||||
return res.pathname, res.checksum
|
||||
}, func(i int, d pkg.Artifact) {
|
||||
r := pkg.Encode(cm.c.Ident(d).Value())
|
||||
r := pkg.Encode(cache.Ident(d).Value())
|
||||
if s, ok := d.(fmt.Stringer); ok {
|
||||
if name := s.String(); name != "" {
|
||||
r += "-" + name
|
||||
@@ -663,19 +663,22 @@ func main() {
|
||||
}
|
||||
return z.Wait()
|
||||
},
|
||||
).Flag(
|
||||
&flagNet,
|
||||
"net", command.BoolFlag(false),
|
||||
"Share host net namespace",
|
||||
).Flag(
|
||||
&flagSession,
|
||||
"session", command.BoolFlag(true),
|
||||
"Retain session",
|
||||
).Flag(
|
||||
&flagWithToolchain,
|
||||
"with-toolchain", command.BoolFlag(false),
|
||||
"Include the stage2 LLVM toolchain",
|
||||
)
|
||||
).
|
||||
Flag(
|
||||
&flagNet,
|
||||
"net", command.BoolFlag(false),
|
||||
"Share host net namespace",
|
||||
).
|
||||
Flag(
|
||||
&flagSession,
|
||||
"session", command.BoolFlag(true),
|
||||
"Retain session",
|
||||
).
|
||||
Flag(
|
||||
&flagWithToolchain,
|
||||
"with-toolchain", command.BoolFlag(false),
|
||||
"Include the stage2 LLVM toolchain",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -686,7 +689,9 @@ func main() {
|
||||
)
|
||||
|
||||
c.MustParse(os.Args[1:], func(err error) {
|
||||
cm.Close()
|
||||
if cache != nil {
|
||||
cache.Close()
|
||||
}
|
||||
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
|
||||
176
cmd/pkgserver/api.go
Normal file
176
cmd/pkgserver/api.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/rosa"
|
||||
)
|
||||
|
||||
// for lazy initialisation of serveInfo
|
||||
var (
|
||||
infoPayload struct {
|
||||
// Current package count.
|
||||
Count int `json:"count"`
|
||||
// Hakurei version, set at link time.
|
||||
HakureiVersion string `json:"hakurei_version"`
|
||||
}
|
||||
infoPayloadOnce sync.Once
|
||||
)
|
||||
|
||||
// handleInfo writes constant system information.
|
||||
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
infoPayloadOnce.Do(func() {
|
||||
infoPayload.Count = int(rosa.PresetUnexportedStart)
|
||||
infoPayload.HakureiVersion = info.Version()
|
||||
})
|
||||
// TODO(mae): cache entire response if no additional fields are planned
|
||||
writeAPIPayload(w, infoPayload)
|
||||
}
|
||||
|
||||
// newStatusHandler returns a [http.HandlerFunc] that offers status files for
|
||||
// viewing or download, if available.
|
||||
func (index *packageIndex) newStatusHandler(disposition bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m, ok := index.names[path.Base(r.URL.Path)]
|
||||
if !ok || !m.HasReport {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := "text/plain; charset=utf-8"
|
||||
if disposition {
|
||||
contentType = "application/octet-stream"
|
||||
|
||||
// quoting like this is unsound, but okay, because metadata is hardcoded
|
||||
contentDisposition := `attachment; filename="`
|
||||
contentDisposition += m.Name + "-"
|
||||
if m.Version != "" {
|
||||
contentDisposition += m.Version + "-"
|
||||
}
|
||||
contentDisposition += m.ids + `.log"`
|
||||
w.Header().Set("Content-Disposition", contentDisposition)
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
if err := func() (err error) {
|
||||
defer index.handleAccess(&err)()
|
||||
_, err = w.Write(m.status)
|
||||
return
|
||||
}(); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(
|
||||
w, "cannot deliver status, contact maintainers",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleGet writes a slice of metadata with specified order.
|
||||
func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
limit, err := strconv.Atoi(q.Get("limit"))
|
||||
if err != nil || limit > 100 || limit < 1 {
|
||||
http.Error(
|
||||
w, "limit must be an integer between 1 and 100",
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
i, err := strconv.Atoi(q.Get("index"))
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
sort, err := strconv.Atoi(q.Get("sort"))
|
||||
if err != nil || sort >= len(index.sorts) || sort < 0 {
|
||||
http.Error(
|
||||
w, "sort must be an integer between 0 and "+
|
||||
strconv.Itoa(sortOrderEnd),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
values := index.sorts[sort][i:min(i+limit, len(index.sorts[sort]))]
|
||||
writeAPIPayload(w, &struct {
|
||||
Values []*metadata `json:"values"`
|
||||
}{values})
|
||||
}
|
||||
|
||||
func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
limit, err := strconv.Atoi(q.Get("limit"))
|
||||
if err != nil || limit > 100 || limit < 1 {
|
||||
http.Error(
|
||||
w, "limit must be an integer between 1 and 100",
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
i, err := strconv.Atoi(q.Get("index"))
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
search, err := url.PathUnescape(q.Get("search"))
|
||||
if len(search) > 100 || err != nil {
|
||||
http.Error(
|
||||
w, "search must be a string between 0 and 100 characters long",
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
desc := q.Get("desc") == "true"
|
||||
n, res, err := index.performSearchQuery(limit, i, search, desc)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
writeAPIPayload(w, &struct {
|
||||
Count int `json:"count"`
|
||||
Results []searchResult `json:"results"`
|
||||
}{n, res})
|
||||
}
|
||||
|
||||
// apiVersion is the name of the current API revision, as part of the pattern.
|
||||
const apiVersion = "v1"
|
||||
|
||||
// registerAPI registers API handler functions.
|
||||
func (index *packageIndex) registerAPI(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /api/"+apiVersion+"/info", handleInfo)
|
||||
mux.HandleFunc("GET /api/"+apiVersion+"/get", index.handleGet)
|
||||
mux.HandleFunc("GET /api/"+apiVersion+"/search", index.handleSearch)
|
||||
mux.HandleFunc("GET /api/"+apiVersion+"/status/", index.newStatusHandler(false))
|
||||
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
||||
}
|
||||
|
||||
// writeAPIPayload sets headers common to API responses and encodes payload as
|
||||
// JSON for the response body.
|
||||
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(
|
||||
w, "cannot encode payload, contact maintainers",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
}
|
||||
}
|
||||
183
cmd/pkgserver/api_test.go
Normal file
183
cmd/pkgserver/api_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/rosa"
|
||||
)
|
||||
|
||||
// prefix is prepended to every API path.
|
||||
const prefix = "/api/" + apiVersion + "/"
|
||||
|
||||
func TestAPIInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handleInfo(w, httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodGet,
|
||||
prefix+"info",
|
||||
nil,
|
||||
))
|
||||
|
||||
resp := w.Result()
|
||||
checkStatus(t, resp, http.StatusOK)
|
||||
checkAPIHeader(t, w.Header())
|
||||
|
||||
checkPayload(t, resp, struct {
|
||||
Count int `json:"count"`
|
||||
HakureiVersion string `json:"hakurei_version"`
|
||||
}{int(rosa.PresetUnexportedStart), info.Version()})
|
||||
}
|
||||
|
||||
func TestAPIGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
const target = prefix + "get"
|
||||
|
||||
index := newIndex(t)
|
||||
newRequest := func(suffix string) *httptest.ResponseRecorder {
|
||||
w := httptest.NewRecorder()
|
||||
index.handleGet(w, httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodGet,
|
||||
target+suffix,
|
||||
nil,
|
||||
))
|
||||
return w
|
||||
}
|
||||
|
||||
checkValidate := func(t *testing.T, suffix string, vmin, vmax int, wantErr string) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := newRequest("?" + suffix + "=invalid")
|
||||
resp := w.Result()
|
||||
checkError(t, resp, wantErr, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("min", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := newRequest("?" + suffix + "=" + strconv.Itoa(vmin-1))
|
||||
resp := w.Result()
|
||||
checkError(t, resp, wantErr, http.StatusBadRequest)
|
||||
|
||||
w = newRequest("?" + suffix + "=" + strconv.Itoa(vmin))
|
||||
resp = w.Result()
|
||||
checkStatus(t, resp, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("max", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := newRequest("?" + suffix + "=" + strconv.Itoa(vmax+1))
|
||||
resp := w.Result()
|
||||
checkError(t, resp, wantErr, http.StatusBadRequest)
|
||||
|
||||
w = newRequest("?" + suffix + "=" + strconv.Itoa(vmax))
|
||||
resp = w.Result()
|
||||
checkStatus(t, resp, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("limit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkValidate(
|
||||
t, "index=0&sort=0&limit", 1, 100,
|
||||
"limit must be an integer between 1 and 100",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("index", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkValidate(
|
||||
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
|
||||
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("sort", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkValidate(
|
||||
t, "index=0&limit=1&sort", 0, int(sortOrderEnd),
|
||||
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
||||
)
|
||||
})
|
||||
|
||||
checkWithSuffix := func(name, suffix string, want []*metadata) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := newRequest(suffix)
|
||||
resp := w.Result()
|
||||
checkStatus(t, resp, http.StatusOK)
|
||||
checkAPIHeader(t, w.Header())
|
||||
checkPayloadFunc(t, resp, func(got *struct {
|
||||
Count int `json:"count"`
|
||||
Values []*metadata `json:"values"`
|
||||
}) bool {
|
||||
return got.Count == len(want) &&
|
||||
slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
||||
return (a.Version == b.Version ||
|
||||
a.Version == rosa.Unversioned ||
|
||||
b.Version == rosa.Unversioned) &&
|
||||
a.HasReport == b.HasReport &&
|
||||
a.Name == b.Name &&
|
||||
a.Description == b.Description &&
|
||||
a.Website == b.Website
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
checkWithSuffix("declarationAscending", "?limit=2&index=0&sort=0", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(0),
|
||||
Version: rosa.Std.Version(0),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(1),
|
||||
Version: rosa.Std.Version(1),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(5),
|
||||
Version: rosa.Std.Version(5),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(6),
|
||||
Version: rosa.Std.Version(6),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(7),
|
||||
Version: rosa.Std.Version(7),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38),
|
||||
},
|
||||
})
|
||||
}
|
||||
105
cmd/pkgserver/index.go
Normal file
105
cmd/pkgserver/index.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
)
|
||||
|
||||
const (
|
||||
declarationAscending = iota
|
||||
declarationDescending
|
||||
nameAscending
|
||||
nameDescending
|
||||
sizeAscending
|
||||
sizeDescending
|
||||
|
||||
sortOrderEnd = iota - 1
|
||||
)
|
||||
|
||||
// packageIndex refers to metadata by name and various sort orders.
|
||||
type packageIndex struct {
|
||||
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
|
||||
names map[string]*metadata
|
||||
search searchCache
|
||||
// Taken from [rosa.Report] if available.
|
||||
handleAccess func(*error) func()
|
||||
}
|
||||
|
||||
// metadata holds [rosa.Metadata] extended with additional information.
|
||||
type metadata struct {
|
||||
p rosa.PArtifact
|
||||
*rosa.Metadata
|
||||
|
||||
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
|
||||
// to the zero value. Otherwise, the zero value is invalid.
|
||||
Version string `json:"version,omitempty"`
|
||||
// Output data size, available if present in report.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
// Whether the underlying [pkg.Artifact] is present in the report.
|
||||
HasReport bool `json:"report"`
|
||||
|
||||
// Ident string encoded ahead of time.
|
||||
ids string
|
||||
// Backed by [rosa.Report], access must be prepared by HandleAccess.
|
||||
status []byte
|
||||
}
|
||||
|
||||
// populate deterministically populates packageIndex, optionally with a report.
|
||||
func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err error) {
|
||||
if report != nil {
|
||||
defer report.HandleAccess(&err)()
|
||||
index.handleAccess = report.HandleAccess
|
||||
}
|
||||
|
||||
var work [rosa.PresetUnexportedStart]*metadata
|
||||
index.names = make(map[string]*metadata)
|
||||
for p := range rosa.PresetUnexportedStart {
|
||||
m := metadata{
|
||||
p: p,
|
||||
|
||||
Metadata: rosa.GetMetadata(p),
|
||||
Version: rosa.Std.Version(p),
|
||||
}
|
||||
if m.Version == "" {
|
||||
return errors.New("invalid version from " + m.Name)
|
||||
}
|
||||
if m.Version == rosa.Unversioned {
|
||||
m.Version = ""
|
||||
}
|
||||
|
||||
if cache != nil && report != nil {
|
||||
id := cache.Ident(rosa.Std.Load(p))
|
||||
m.ids = pkg.Encode(id.Value())
|
||||
m.status, m.Size = report.ArtifactOf(id)
|
||||
m.HasReport = m.Size >= 0
|
||||
}
|
||||
|
||||
work[p] = &m
|
||||
index.names[m.Name] = &m
|
||||
}
|
||||
|
||||
index.sorts[declarationAscending] = work
|
||||
index.sorts[declarationDescending] = work
|
||||
slices.Reverse(index.sorts[declarationDescending][:])
|
||||
|
||||
index.sorts[nameAscending] = work
|
||||
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
index.sorts[nameDescending] = index.sorts[nameAscending]
|
||||
slices.Reverse(index.sorts[nameDescending][:])
|
||||
|
||||
index.sorts[sizeAscending] = work
|
||||
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
||||
return cmp.Compare(a.Size, b.Size)
|
||||
})
|
||||
index.sorts[sizeDescending] = index.sorts[sizeAscending]
|
||||
slices.Reverse(index.sorts[sizeDescending][:])
|
||||
|
||||
return
|
||||
}
|
||||
114
cmd/pkgserver/main.go
Normal file
114
cmd/pkgserver/main.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const shutdownTimeout = 15 * time.Second
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("pkgserver: ")
|
||||
|
||||
var (
|
||||
flagBaseDir string
|
||||
flagAddr string
|
||||
)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer stop()
|
||||
msg := message.New(log.Default())
|
||||
|
||||
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
||||
var (
|
||||
cache *pkg.Cache
|
||||
report *rosa.Report
|
||||
)
|
||||
switch len(args) {
|
||||
case 0:
|
||||
break
|
||||
|
||||
case 1:
|
||||
baseDir, err := check.NewAbs(flagBaseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache, err = pkg.Open(ctx, msg, 0, baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cache.Close()
|
||||
|
||||
report, err = rosa.OpenReport(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.New("pkgserver requires 1 argument")
|
||||
|
||||
}
|
||||
|
||||
var index packageIndex
|
||||
index.search = make(searchCache)
|
||||
if err := index.populate(cache, report); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
index.search.clean()
|
||||
}
|
||||
}
|
||||
}()
|
||||
var mux http.ServeMux
|
||||
uiRoutes(&mux)
|
||||
index.registerAPI(&mux)
|
||||
server := http.Server{
|
||||
Addr: flagAddr,
|
||||
Handler: &mux,
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(c); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
return server.ListenAndServe()
|
||||
}).Flag(
|
||||
&flagBaseDir,
|
||||
"b", command.StringFlag(""),
|
||||
"base directory for cache",
|
||||
).Flag(
|
||||
&flagAddr,
|
||||
"addr", command.StringFlag(":8067"),
|
||||
"TCP network address to listen on",
|
||||
)
|
||||
c.MustParse(os.Args[1:], func(err error) {
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
os.Exit(0)
|
||||
}
|
||||
log.Fatal(err)
|
||||
})
|
||||
}
|
||||
96
cmd/pkgserver/main_test.go
Normal file
96
cmd/pkgserver/main_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// newIndex returns the address of a newly populated packageIndex.
|
||||
func newIndex(t *testing.T) *packageIndex {
|
||||
t.Helper()
|
||||
|
||||
var index packageIndex
|
||||
if err := index.populate(nil, nil); err != nil {
|
||||
t.Fatalf("populate: error = %v", err)
|
||||
}
|
||||
return &index
|
||||
}
|
||||
|
||||
// checkStatus checks response status code.
|
||||
func checkStatus(t *testing.T, resp *http.Response, want int) {
|
||||
t.Helper()
|
||||
|
||||
if resp.StatusCode != want {
|
||||
t.Errorf(
|
||||
"StatusCode: %s, want %s",
|
||||
http.StatusText(resp.StatusCode),
|
||||
http.StatusText(want),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// checkHeader checks the value of a header entry.
|
||||
func checkHeader(t *testing.T, h http.Header, key, want string) {
|
||||
t.Helper()
|
||||
|
||||
if got := h.Get(key); got != want {
|
||||
t.Errorf("%s: %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// checkAPIHeader checks common entries set for API endpoints.
|
||||
func checkAPIHeader(t *testing.T, h http.Header) {
|
||||
t.Helper()
|
||||
|
||||
checkHeader(t, h, "Content-Type", "application/json; charset=utf-8")
|
||||
checkHeader(t, h, "Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
checkHeader(t, h, "Pragma", "no-cache")
|
||||
checkHeader(t, h, "Expires", "0")
|
||||
}
|
||||
|
||||
// checkPayloadFunc checks the JSON response of an API endpoint by passing it to f.
|
||||
func checkPayloadFunc[T any](
|
||||
t *testing.T,
|
||||
resp *http.Response,
|
||||
f func(got *T) bool,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
var got T
|
||||
r := io.Reader(resp.Body)
|
||||
if testing.Verbose() {
|
||||
var buf bytes.Buffer
|
||||
r = io.TeeReader(r, &buf)
|
||||
defer func() { t.Helper(); t.Log(buf.String()) }()
|
||||
}
|
||||
if err := json.NewDecoder(r).Decode(&got); err != nil {
|
||||
t.Fatalf("Decode: error = %v", err)
|
||||
}
|
||||
|
||||
if !f(&got) {
|
||||
t.Errorf("Body: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// checkPayload checks the JSON response of an API endpoint.
|
||||
func checkPayload[T any](t *testing.T, resp *http.Response, want T) {
|
||||
t.Helper()
|
||||
|
||||
checkPayloadFunc(t, resp, func(got *T) bool {
|
||||
return reflect.DeepEqual(got, &want)
|
||||
})
|
||||
}
|
||||
|
||||
func checkError(t *testing.T, resp *http.Response, error string, code int) {
|
||||
t.Helper()
|
||||
|
||||
checkStatus(t, resp, code)
|
||||
if got, _ := io.ReadAll(resp.Body); string(got) != fmt.Sprintln(error) {
|
||||
t.Errorf("Body: %q, want %q", string(got), error)
|
||||
}
|
||||
}
|
||||
77
cmd/pkgserver/search.go
Normal file
77
cmd/pkgserver/search.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
type searchCache map[string]searchCacheEntry
|
||||
type searchResult struct {
|
||||
NameIndices [][]int `json:"name_matches"`
|
||||
DescIndices [][]int `json:"desc_matches,omitempty"`
|
||||
Score float64 `json:"score"`
|
||||
*metadata
|
||||
}
|
||||
type searchCacheEntry struct {
|
||||
query string
|
||||
results []searchResult
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
func (index *packageIndex) performSearchQuery(limit int, i int, search string, desc bool) (int, []searchResult, error) {
|
||||
entry, ok := index.search[search]
|
||||
if ok {
|
||||
return len(entry.results), entry.results[i:min(i+limit, len(entry.results))], nil
|
||||
}
|
||||
|
||||
regex, err := regexp.Compile(search)
|
||||
if err != nil {
|
||||
return 0, make([]searchResult, 0), err
|
||||
}
|
||||
res := make([]searchResult, 0)
|
||||
for p := range maps.Values(index.names) {
|
||||
nameIndices := regex.FindAllIndex([]byte(p.Name), -1)
|
||||
var descIndices [][]int = nil
|
||||
if desc {
|
||||
descIndices = regex.FindAllIndex([]byte(p.Description), -1)
|
||||
}
|
||||
if nameIndices == nil && descIndices == nil {
|
||||
continue
|
||||
}
|
||||
score := float64(indexsum(nameIndices)) / (float64(len(nameIndices)) + 1)
|
||||
if desc {
|
||||
score += float64(indexsum(descIndices)) / (float64(len(descIndices)) + 1) / 10.0
|
||||
}
|
||||
res = append(res, searchResult{
|
||||
NameIndices: nameIndices,
|
||||
DescIndices: descIndices,
|
||||
Score: score,
|
||||
metadata: p,
|
||||
})
|
||||
}
|
||||
slices.SortFunc(res[:], func(a, b searchResult) int { return -cmp.Compare(a.Score, b.Score) })
|
||||
expiry := time.Now().Add(1 * time.Minute)
|
||||
entry = searchCacheEntry{
|
||||
query: search,
|
||||
results: res,
|
||||
expiry: expiry,
|
||||
}
|
||||
index.search[search] = entry
|
||||
|
||||
return len(res), res[i:min(i+limit, len(entry.results))], nil
|
||||
}
|
||||
func (s *searchCache) clean() {
|
||||
maps.DeleteFunc(*s, func(_ string, v searchCacheEntry) bool {
|
||||
return v.expiry.Before(time.Now())
|
||||
})
|
||||
}
|
||||
func indexsum(in [][]int) int {
|
||||
sum := 0
|
||||
for i := 0; i < len(in); i++ {
|
||||
sum += in[i][1] - in[i][0]
|
||||
}
|
||||
return sum
|
||||
}
|
||||
38
cmd/pkgserver/ui.go
Normal file
38
cmd/pkgserver/ui.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-XSS-Protection", "1")
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
|
||||
http.ServeFileFS(w, r, content, "ui/index.html")
|
||||
}
|
||||
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/static/style.css":
|
||||
darkTheme := r.CookiesNamed("dark_theme")
|
||||
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
|
||||
http.ServeFileFS(w, r, content, "ui/static/dark.css")
|
||||
} else {
|
||||
http.ServeFileFS(w, r, content, "ui/static/light.css")
|
||||
}
|
||||
case "/favicon.ico":
|
||||
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
||||
case "/static/index.js":
|
||||
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func uiRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /{$}", serveWebUI)
|
||||
mux.HandleFunc("GET /favicon.ico", serveStaticContent)
|
||||
mux.HandleFunc("GET /static/", serveStaticContent)
|
||||
}
|
||||
35
cmd/pkgserver/ui/index.html
Normal file
35
cmd/pkgserver/ui/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="static/style.css">
|
||||
<title>Hakurei PkgServer</title>
|
||||
<script src="static/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hakurei PkgServer</h1>
|
||||
|
||||
<table id="pkg-list">
|
||||
<tr><td>Loading...</td></tr>
|
||||
</table>
|
||||
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||
<span class="bottom-nav"><a href="javascript:prevPage()">« Previous</a> <span id="page-number">1</span> <a href="javascript:nextPage()">Next »</a></span>
|
||||
<span><label for="count">Entries per page: </label><select name="count" id="count">
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="50">50</option>
|
||||
</select></span>
|
||||
<span><label for="sort">Sort by: </label><select name="sort" id="sort">
|
||||
<option value="0">Definition (ascending)</option>
|
||||
<option value="1">Definition (descending)</option>
|
||||
<option value="2">Name (ascending)</option>
|
||||
<option value="3">Name (descending)</option>
|
||||
<option value="4">Size (ascending)</option>
|
||||
<option value="5">Size (descending)</option>
|
||||
</select></span>
|
||||
</body>
|
||||
<footer>
|
||||
<p>©<a href="https://hakurei.app/">Hakurei</a> (<span id="hakurei-version">unknown</span>). Licensed under the MIT license.</p>
|
||||
</footer>
|
||||
</html>
|
||||
0
cmd/pkgserver/ui/static/_common.scss
Normal file
0
cmd/pkgserver/ui/static/_common.scss
Normal file
6
cmd/pkgserver/ui/static/dark.scss
Normal file
6
cmd/pkgserver/ui/static/dark.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@use 'common';
|
||||
|
||||
html {
|
||||
background-color: #2c2c2c;
|
||||
color: ghostwhite;
|
||||
}
|
||||
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
161
cmd/pkgserver/ui/static/index.ts
Normal file
161
cmd/pkgserver/ui/static/index.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
function assertGetElementById(id: string): HTMLElement {
|
||||
let elem = document.getElementById(id)
|
||||
if(elem == null) throw new ReferenceError(`element with ID '${id}' missing from DOM`)
|
||||
return elem
|
||||
}
|
||||
|
||||
interface PackageIndexEntry {
|
||||
name: string
|
||||
size: number | null
|
||||
description: string | null
|
||||
website: string | null
|
||||
version: string | null
|
||||
report: boolean
|
||||
}
|
||||
function toHTML(entry: PackageIndexEntry): HTMLTableRowElement {
|
||||
let v = entry.version != null ? `<span>${escapeHtml(entry.version)}</span>` : ""
|
||||
let s = entry.size != null ? `<p>Size: ${toByteSizeString(entry.size)} (${entry.size})</p>` : ""
|
||||
let d = entry.description != null ? `<p>${escapeHtml(entry.description)}</p>` : ""
|
||||
let w = entry.website != null ? `<a href="${encodeURI(entry.website)}">Website</a>` : ""
|
||||
let r = entry.report ? `Log (<a href=\"${encodeURI('/api/v1/status/' + entry.name)}\">View</a> | <a href=\"${encodeURI('/status/' + entry.name)}\">Download</a>)` : ""
|
||||
let row = <HTMLTableRowElement>(document.createElement('tr'))
|
||||
row.innerHTML = `<td>
|
||||
<h2>${escapeHtml(entry.name)} ${v}</h2>
|
||||
${d}
|
||||
${s}
|
||||
${w}
|
||||
${r}
|
||||
</td>`
|
||||
return row
|
||||
}
|
||||
|
||||
function toByteSizeString(bytes: number): string {
|
||||
if(bytes == null || bytes < 1024) return `${bytes}B`
|
||||
if(bytes < Math.pow(1024, 2)) return `${(bytes/1024).toFixed(2)}kiB`
|
||||
if(bytes < Math.pow(1024, 3)) return `${(bytes/Math.pow(1024, 2)).toFixed(2)}MiB`
|
||||
if(bytes < Math.pow(1024, 4)) return `${(bytes/Math.pow(1024, 3)).toFixed(2)}GiB`
|
||||
if(bytes < Math.pow(1024, 5)) return `${(bytes/Math.pow(1024, 4)).toFixed(2)}TiB`
|
||||
return "not only is it big, it's large"
|
||||
}
|
||||
|
||||
const API_VERSION = 1
|
||||
const ENDPOINT = `/api/v${API_VERSION}`
|
||||
interface InfoPayload {
|
||||
count: number
|
||||
hakurei_version: string
|
||||
}
|
||||
|
||||
async function infoRequest(): Promise<InfoPayload> {
|
||||
const res = await fetch(`${ENDPOINT}/info`)
|
||||
const payload = await res.json()
|
||||
return payload
|
||||
}
|
||||
interface GetPayload {
|
||||
values: PackageIndexEntry[]
|
||||
}
|
||||
|
||||
enum SortOrders {
|
||||
DeclarationAscending,
|
||||
DeclarationDescending,
|
||||
NameAscending,
|
||||
NameDescending
|
||||
}
|
||||
async function getRequest(limit: number, index: number, sort: SortOrders): Promise<GetPayload> {
|
||||
const res = await fetch(`${ENDPOINT}/get?limit=${limit}&index=${index}&sort=${sort.valueOf()}`)
|
||||
const payload = await res.json()
|
||||
return payload
|
||||
}
|
||||
class State {
|
||||
entriesPerPage: number = 10
|
||||
entryIndex: number = 0
|
||||
maxEntries: number = 0
|
||||
sort: SortOrders = SortOrders.DeclarationAscending
|
||||
|
||||
getEntriesPerPage(): number {
|
||||
return this.entriesPerPage
|
||||
}
|
||||
setEntriesPerPage(entriesPerPage: number) {
|
||||
this.entriesPerPage = entriesPerPage
|
||||
this.setEntryIndex(Math.floor(this.getEntryIndex() / entriesPerPage) * entriesPerPage)
|
||||
}
|
||||
getEntryIndex(): number {
|
||||
return this.entryIndex
|
||||
}
|
||||
setEntryIndex(entryIndex: number) {
|
||||
this.entryIndex = entryIndex
|
||||
this.updatePage()
|
||||
this.updateRange()
|
||||
this.updateListings()
|
||||
}
|
||||
getMaxEntries(): number {
|
||||
return this.maxEntries
|
||||
}
|
||||
setMaxEntries(max: number) {
|
||||
this.maxEntries = max
|
||||
}
|
||||
getSortOrder(): SortOrders {
|
||||
return this.sort
|
||||
}
|
||||
setSortOrder(sortOrder: SortOrders) {
|
||||
this.sort = sortOrder
|
||||
this.setEntryIndex(0)
|
||||
}
|
||||
updatePage() {
|
||||
let page = Math.ceil(((this.getEntryIndex() + this.getEntriesPerPage()) - 1) / this.getEntriesPerPage())
|
||||
assertGetElementById("page-number").innerText = String(page)
|
||||
}
|
||||
updateRange() {
|
||||
let max = Math.min(this.getEntryIndex() + this.getEntriesPerPage(), this.getMaxEntries())
|
||||
assertGetElementById("entry-counter").innerText = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxEntries()}`
|
||||
}
|
||||
updateListings() {
|
||||
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
|
||||
.then(res => {
|
||||
let table = assertGetElementById("pkg-list")
|
||||
table.innerHTML = ''
|
||||
res.values.forEach((row) => {
|
||||
table.appendChild(toHTML(row))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let STATE: State
|
||||
|
||||
function prevPage() {
|
||||
let index = STATE.getEntryIndex()
|
||||
STATE.setEntryIndex(Math.max(0, index - STATE.getEntriesPerPage()))
|
||||
}
|
||||
function nextPage() {
|
||||
let index = STATE.getEntryIndex()
|
||||
STATE.setEntryIndex(Math.min((Math.ceil(STATE.getMaxEntries() / STATE.getEntriesPerPage()) * STATE.getEntriesPerPage()) - STATE.getEntriesPerPage(), index + STATE.getEntriesPerPage()))
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
if(str === undefined) return ""
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
STATE = new State()
|
||||
infoRequest()
|
||||
.then(res => {
|
||||
STATE.setMaxEntries(res.count)
|
||||
assertGetElementById("hakurei-version").innerText = res.hakurei_version
|
||||
STATE.updateRange()
|
||||
STATE.updateListings()
|
||||
})
|
||||
|
||||
assertGetElementById("count").addEventListener("change", (event) => {
|
||||
STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
|
||||
})
|
||||
assertGetElementById("sort").addEventListener("change", (event) => {
|
||||
STATE.setSortOrder(parseInt((event.target as HTMLSelectElement).value))
|
||||
})
|
||||
})
|
||||
6
cmd/pkgserver/ui/static/light.scss
Normal file
6
cmd/pkgserver/ui/static/light.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@use 'common';
|
||||
|
||||
html {
|
||||
background-color: #d3d3d3;
|
||||
color: black;
|
||||
}
|
||||
6
cmd/pkgserver/ui/static/tsconfig.json
Normal file
6
cmd/pkgserver/ui/static/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "ES2024"
|
||||
}
|
||||
}
|
||||
9
cmd/pkgserver/ui_full.go
Normal file
9
cmd/pkgserver/ui_full.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build frontend
|
||||
|
||||
package main
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc -p ui/static"
|
||||
//go:embed ui/*
|
||||
var content embed.FS
|
||||
7
cmd/pkgserver/ui_stub.go
Normal file
7
cmd/pkgserver/ui_stub.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !frontend
|
||||
|
||||
package main
|
||||
|
||||
import "testing/fstest"
|
||||
|
||||
var content fstest.MapFS
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/landlock"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -308,7 +307,7 @@ func (p *Container) Start() error {
|
||||
done <- func() error {
|
||||
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
||||
// created from the calling thread
|
||||
if err := setNoNewPrivs(); err != nil {
|
||||
if err := SetNoNewPrivs(); err != nil {
|
||||
return &StartError{
|
||||
Fatal: true,
|
||||
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
||||
@@ -318,14 +317,12 @@ func (p *Container) Start() error {
|
||||
|
||||
// landlock: depends on per-thread state but acts on a process group
|
||||
{
|
||||
rulesetAttr := &landlock.RulesetAttr{
|
||||
Scoped: landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||
}
|
||||
rulesetAttr := &RulesetAttr{Scoped: LANDLOCK_SCOPE_SIGNAL}
|
||||
if !p.HostAbstract {
|
||||
rulesetAttr.Scoped |= landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
||||
rulesetAttr.Scoped |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
||||
}
|
||||
|
||||
if abi, err := landlock.GetABI(); err != nil {
|
||||
if abi, err := LandlockGetABI(); err != nil {
|
||||
if p.HostAbstract || !p.HostNet {
|
||||
// landlock can be skipped here as it restricts access
|
||||
// to resources already covered by namespaces (pid, net)
|
||||
@@ -354,7 +351,7 @@ func (p *Container) Start() error {
|
||||
}
|
||||
} else {
|
||||
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
|
||||
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
||||
_ = Close(rulesetFd)
|
||||
return &StartError{
|
||||
Fatal: true,
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/landlock"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/ldd"
|
||||
"hakurei.app/message"
|
||||
@@ -457,7 +456,7 @@ func TestContainer(t *testing.T) {
|
||||
c.RetainSession = tc.session
|
||||
c.HostNet = tc.net
|
||||
if info.CanDegrade {
|
||||
if _, err := landlock.GetABI(); err != nil {
|
||||
if _, err := container.LandlockGetABI(); err != nil {
|
||||
if !errors.Is(err, syscall.ENOSYS) {
|
||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ func (direct) lockOSThread() { runtime.LockOSThread() }
|
||||
|
||||
func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
|
||||
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
||||
func (direct) setNoNewPrivs() error { return setNoNewPrivs() }
|
||||
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
|
||||
|
||||
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
||||
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package landlock
|
||||
package container
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -14,11 +14,11 @@ const (
|
||||
LANDLOCK_CREATE_RULESET_VERSION = 1 << iota
|
||||
)
|
||||
|
||||
// AccessFS is bitmask of handled filesystem actions.
|
||||
type AccessFS uint64
|
||||
// LandlockAccessFS is bitmask of handled filesystem actions.
|
||||
type LandlockAccessFS uint64
|
||||
|
||||
const (
|
||||
LANDLOCK_ACCESS_FS_EXECUTE AccessFS = 1 << iota
|
||||
LANDLOCK_ACCESS_FS_EXECUTE LandlockAccessFS = 1 << iota
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE
|
||||
LANDLOCK_ACCESS_FS_READ_FILE
|
||||
LANDLOCK_ACCESS_FS_READ_DIR
|
||||
@@ -38,8 +38,8 @@ const (
|
||||
_LANDLOCK_ACCESS_FS_DELIM
|
||||
)
|
||||
|
||||
// String returns a space-separated string of [AccessFS] flags.
|
||||
func (f AccessFS) String() string {
|
||||
// String returns a space-separated string of [LandlockAccessFS] flags.
|
||||
func (f LandlockAccessFS) String() string {
|
||||
switch f {
|
||||
case LANDLOCK_ACCESS_FS_EXECUTE:
|
||||
return "execute"
|
||||
@@ -90,8 +90,8 @@ func (f AccessFS) String() string {
|
||||
return "fs_ioctl_dev"
|
||||
|
||||
default:
|
||||
var c []AccessFS
|
||||
for i := AccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
||||
var c []LandlockAccessFS
|
||||
for i := LandlockAccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
||||
if f&i != 0 {
|
||||
c = append(c, i)
|
||||
}
|
||||
@@ -107,18 +107,18 @@ func (f AccessFS) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// AccessNet is bitmask of handled network actions.
|
||||
type AccessNet uint64
|
||||
// LandlockAccessNet is bitmask of handled network actions.
|
||||
type LandlockAccessNet uint64
|
||||
|
||||
const (
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP AccessNet = 1 << iota
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP LandlockAccessNet = 1 << iota
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP
|
||||
|
||||
_LANDLOCK_ACCESS_NET_DELIM
|
||||
)
|
||||
|
||||
// String returns a space-separated string of [AccessNet] flags.
|
||||
func (f AccessNet) String() string {
|
||||
// String returns a space-separated string of [LandlockAccessNet] flags.
|
||||
func (f LandlockAccessNet) String() string {
|
||||
switch f {
|
||||
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
||||
return "bind_tcp"
|
||||
@@ -127,8 +127,8 @@ func (f AccessNet) String() string {
|
||||
return "connect_tcp"
|
||||
|
||||
default:
|
||||
var c []AccessNet
|
||||
for i := AccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
||||
var c []LandlockAccessNet
|
||||
for i := LandlockAccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
||||
if f&i != 0 {
|
||||
c = append(c, i)
|
||||
}
|
||||
@@ -144,18 +144,18 @@ func (f AccessNet) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Scope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
||||
type Scope uint64
|
||||
// LandlockScope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
||||
type LandlockScope uint64
|
||||
|
||||
const (
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET Scope = 1 << iota
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LandlockScope = 1 << iota
|
||||
LANDLOCK_SCOPE_SIGNAL
|
||||
|
||||
_LANDLOCK_SCOPE_DELIM
|
||||
)
|
||||
|
||||
// String returns a space-separated string of [Scope] flags.
|
||||
func (f Scope) String() string {
|
||||
// String returns a space-separated string of [LandlockScope] flags.
|
||||
func (f LandlockScope) String() string {
|
||||
switch f {
|
||||
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||
return "abstract_unix_socket"
|
||||
@@ -164,8 +164,8 @@ func (f Scope) String() string {
|
||||
return "signal"
|
||||
|
||||
default:
|
||||
var c []Scope
|
||||
for i := Scope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
||||
var c []LandlockScope
|
||||
for i := LandlockScope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
||||
if f&i != 0 {
|
||||
c = append(c, i)
|
||||
}
|
||||
@@ -184,12 +184,12 @@ func (f Scope) String() string {
|
||||
// RulesetAttr is equivalent to struct landlock_ruleset_attr.
|
||||
type RulesetAttr struct {
|
||||
// Bitmask of handled filesystem actions.
|
||||
HandledAccessFS AccessFS
|
||||
HandledAccessFS LandlockAccessFS
|
||||
// Bitmask of handled network actions.
|
||||
HandledAccessNet AccessNet
|
||||
HandledAccessNet LandlockAccessNet
|
||||
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
||||
// resources (e.g. IPCs).
|
||||
Scoped Scope
|
||||
Scoped LandlockScope
|
||||
}
|
||||
|
||||
// String returns a user-facing description of [RulesetAttr].
|
||||
@@ -239,13 +239,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// GetABI returns the ABI version supported by the kernel.
|
||||
func GetABI() (int, error) {
|
||||
// LandlockGetABI returns the ABI version supported by the kernel.
|
||||
func LandlockGetABI() (int, error) {
|
||||
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
||||
}
|
||||
|
||||
// RestrictSelf applies a loaded ruleset to the calling thread.
|
||||
func RestrictSelf(rulesetFd int, flags uintptr) error {
|
||||
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
|
||||
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
|
||||
r, _, errno := syscall.Syscall(
|
||||
ext.SYS_LANDLOCK_RESTRICT_SELF,
|
||||
uintptr(rulesetFd),
|
||||
65
container/landlock_test.go
Normal file
65
container/landlock_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func TestLandlockString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
rulesetAttr *container.RulesetAttr
|
||||
want string
|
||||
}{
|
||||
{"nil", nil, "NULL"},
|
||||
{"zero", new(container.RulesetAttr), "0"},
|
||||
{"some", &container.RulesetAttr{Scoped: container.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
||||
{"set", &container.RulesetAttr{
|
||||
HandledAccessFS: container.LANDLOCK_ACCESS_FS_MAKE_SYM | container.LANDLOCK_ACCESS_FS_IOCTL_DEV | container.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | container.LANDLOCK_SCOPE_SIGNAL,
|
||||
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
||||
{"all", &container.RulesetAttr{
|
||||
HandledAccessFS: container.LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
container.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||
container.LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
container.LANDLOCK_ACCESS_FS_READ_DIR |
|
||||
container.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||
container.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||
container.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||
container.LANDLOCK_ACCESS_FS_REFER |
|
||||
container.LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
container.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
container.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||
container.LANDLOCK_SCOPE_SIGNAL,
|
||||
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.rulesetAttr.String(); got != tc.want {
|
||||
t.Errorf("String: %s, want %s", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLandlockAttrSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := 24
|
||||
if got := unsafe.Sizeof(container.RulesetAttr{}); got != uintptr(want) {
|
||||
t.Errorf("Sizeof: %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"hakurei.app/ext"
|
||||
)
|
||||
|
||||
// setNoNewPrivs sets the calling thread's no_new_privs attribute.
|
||||
func setNoNewPrivs() error {
|
||||
// SetNoNewPrivs sets the calling thread's no_new_privs attribute.
|
||||
func SetNoNewPrivs() error {
|
||||
return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -140,29 +140,21 @@ var (
|
||||
ErrInsecure = errors.New("configuration is insecure")
|
||||
)
|
||||
|
||||
const (
|
||||
// VAllowInsecure allows use of compatibility options considered insecure
|
||||
// under any configuration, to work around ecosystem-wide flaws.
|
||||
VAllowInsecure = 1 << iota
|
||||
)
|
||||
|
||||
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||
func (config *Config) Validate(flags int) error {
|
||||
const step = "validate configuration"
|
||||
|
||||
func (config *Config) Validate() error {
|
||||
if config == nil {
|
||||
return &AppError{Step: step, Err: ErrConfigNull,
|
||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||
Msg: "invalid configuration"}
|
||||
}
|
||||
|
||||
// this is checked again in hsu
|
||||
if config.Identity < IdentityStart || config.Identity > IdentityEnd {
|
||||
return &AppError{Step: step, Err: ErrIdentityBounds,
|
||||
return &AppError{Step: "validate configuration", Err: ErrIdentityBounds,
|
||||
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
||||
}
|
||||
|
||||
if config.SchedPolicy < 0 || config.SchedPolicy > ext.SCHED_LAST {
|
||||
return &AppError{Step: step, Err: ErrSchedPolicyBounds,
|
||||
return &AppError{Step: "validate configuration", Err: ErrSchedPolicyBounds,
|
||||
Msg: "scheduling policy " +
|
||||
strconv.Itoa(int(config.SchedPolicy)) +
|
||||
" out of range"}
|
||||
@@ -176,51 +168,34 @@ func (config *Config) Validate(flags int) error {
|
||||
}
|
||||
|
||||
if config.Container == nil {
|
||||
return &AppError{Step: step, Err: ErrConfigNull,
|
||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||
Msg: "configuration missing container state"}
|
||||
}
|
||||
if config.Container.Home == nil {
|
||||
return &AppError{Step: step, Err: ErrConfigNull,
|
||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||
Msg: "container configuration missing path to home directory"}
|
||||
}
|
||||
if config.Container.Shell == nil {
|
||||
return &AppError{Step: step, Err: ErrConfigNull,
|
||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||
Msg: "container configuration missing path to shell"}
|
||||
}
|
||||
if config.Container.Path == nil {
|
||||
return &AppError{Step: step, Err: ErrConfigNull,
|
||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||
Msg: "container configuration missing path to initial program"}
|
||||
}
|
||||
|
||||
for key := range config.Container.Env {
|
||||
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
||||
return &AppError{Step: step, Err: ErrEnviron,
|
||||
return &AppError{Step: "validate configuration", Err: ErrEnviron,
|
||||
Msg: "invalid environment variable " + strconv.Quote(key)}
|
||||
}
|
||||
}
|
||||
|
||||
et := config.Enablements.Unwrap()
|
||||
if !config.DirectPulse && et&EPulse != 0 {
|
||||
return &AppError{Step: step, Err: ErrInsecure,
|
||||
if et := config.Enablements.Unwrap(); !config.DirectPulse && et&EPulse != 0 {
|
||||
return &AppError{Step: "validate configuration", Err: ErrInsecure,
|
||||
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
||||
}
|
||||
|
||||
if flags&VAllowInsecure == 0 {
|
||||
switch {
|
||||
case et&EWayland != 0 && config.DirectWayland:
|
||||
return &AppError{Step: step, Err: ErrInsecure,
|
||||
Msg: "direct_wayland is insecure and no longer supported"}
|
||||
|
||||
case et&EPipeWire != 0 && config.DirectPipeWire:
|
||||
return &AppError{Step: step, Err: ErrInsecure,
|
||||
Msg: "direct_pipewire is insecure and no longer supported"}
|
||||
|
||||
case et&EPulse != 0 && config.DirectPulse:
|
||||
return &AppError{Step: step, Err: ErrInsecure,
|
||||
Msg: "direct_pulse is insecure and no longer supported"}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,109 +14,65 @@ func TestConfigValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *hst.Config
|
||||
flags int
|
||||
wantErr error
|
||||
}{
|
||||
{"nil", nil, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
{"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "invalid configuration"}},
|
||||
|
||||
{"identity lower", &hst.Config{Identity: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||
{"identity lower", &hst.Config{Identity: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||
Msg: "identity -1 out of range"}},
|
||||
{"identity upper", &hst.Config{Identity: 10000}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||
Msg: "identity 10000 out of range"}},
|
||||
|
||||
{"sched lower", &hst.Config{SchedPolicy: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||
{"sched lower", &hst.Config{SchedPolicy: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||
Msg: "scheduling policy -1 out of range"}},
|
||||
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||
Msg: "scheduling policy 51966 out of range"}},
|
||||
|
||||
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}}, 0,
|
||||
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
|
||||
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
||||
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}}, 0,
|
||||
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},
|
||||
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
||||
|
||||
{"container", &hst.Config{}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
{"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "configuration missing container state"}},
|
||||
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "container configuration missing path to home directory"}},
|
||||
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "container configuration missing path to shell"}},
|
||||
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "container configuration missing path to initial program"}},
|
||||
|
||||
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
Env: map[string]string{"TERM=": ""},
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||
Msg: `invalid environment variable "TERM="`}},
|
||||
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
Env: map[string]string{"TERM\x00": ""},
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||
Msg: `invalid environment variable "TERM\x00"`}},
|
||||
|
||||
{"insecure pulse", &hst.Config{Enablements: new(hst.EPulse), Container: &hst.ContainerConfig{
|
||||
{"insecure pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
||||
|
||||
{"direct wayland", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||
Msg: "direct_wayland is insecure and no longer supported"}},
|
||||
{"direct wayland allow", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, hst.VAllowInsecure, nil},
|
||||
|
||||
{"direct pipewire", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||
Msg: "direct_pipewire is insecure and no longer supported"}},
|
||||
{"direct pipewire allow", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, hst.VAllowInsecure, nil},
|
||||
|
||||
{"direct pulse", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||
Msg: "direct_pulse is insecure and no longer supported"}},
|
||||
{"direct pulse allow", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, hst.VAllowInsecure, nil},
|
||||
|
||||
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||
Home: fhs.AbsTmp,
|
||||
Shell: fhs.AbsTmp,
|
||||
Path: fhs.AbsTmp,
|
||||
}}, 0, nil},
|
||||
}}, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := tc.config.Validate(tc.flags); !reflect.DeepEqual(err, tc.wantErr) {
|
||||
if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Enablements denotes optional host service to export to the target user.
|
||||
type Enablements byte
|
||||
// Enablement represents an optional host service to export to the target user.
|
||||
type Enablement byte
|
||||
|
||||
const (
|
||||
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
||||
EWayland Enablements = 1 << iota
|
||||
EWayland Enablement = 1 << iota
|
||||
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
||||
// pathname socket.
|
||||
EX11
|
||||
@@ -28,8 +28,8 @@ const (
|
||||
EM
|
||||
)
|
||||
|
||||
// String returns a string representation of the flags set on [Enablements].
|
||||
func (e Enablements) String() string {
|
||||
// String returns a string representation of the flags set on [Enablement].
|
||||
func (e Enablement) String() string {
|
||||
switch e {
|
||||
case 0:
|
||||
return "(no enablements)"
|
||||
@@ -47,7 +47,7 @@ func (e Enablements) String() string {
|
||||
buf := new(strings.Builder)
|
||||
buf.Grow(32)
|
||||
|
||||
for i := Enablements(1); i < EM; i <<= 1 {
|
||||
for i := Enablement(1); i < EM; i <<= 1 {
|
||||
if e&i != 0 {
|
||||
buf.WriteString(", " + i.String())
|
||||
}
|
||||
@@ -60,6 +60,12 @@ func (e Enablements) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnablements returns the address of [Enablement] as [Enablements].
|
||||
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
||||
|
||||
// Enablements is the [json] adapter for [Enablement].
|
||||
type Enablements Enablement
|
||||
|
||||
// enablementsJSON is the [json] representation of [Enablements].
|
||||
type enablementsJSON = struct {
|
||||
Wayland bool `json:"wayland,omitempty"`
|
||||
@@ -69,21 +75,24 @@ type enablementsJSON = struct {
|
||||
Pulse bool `json:"pulse,omitempty"`
|
||||
}
|
||||
|
||||
// Unwrap returns the value pointed to by e.
|
||||
func (e *Enablements) Unwrap() Enablements {
|
||||
// Unwrap returns the underlying [Enablement].
|
||||
func (e *Enablements) Unwrap() Enablement {
|
||||
if e == nil {
|
||||
return 0
|
||||
}
|
||||
return *e
|
||||
return Enablement(*e)
|
||||
}
|
||||
|
||||
func (e Enablements) MarshalJSON() ([]byte, error) {
|
||||
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
||||
if e == nil {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return json.Marshal(&enablementsJSON{
|
||||
Wayland: e&EWayland != 0,
|
||||
X11: e&EX11 != 0,
|
||||
DBus: e&EDBus != 0,
|
||||
PipeWire: e&EPipeWire != 0,
|
||||
Pulse: e&EPulse != 0,
|
||||
Wayland: Enablement(*e)&EWayland != 0,
|
||||
X11: Enablement(*e)&EX11 != 0,
|
||||
DBus: Enablement(*e)&EDBus != 0,
|
||||
PipeWire: Enablement(*e)&EPipeWire != 0,
|
||||
Pulse: Enablement(*e)&EPulse != 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,21 +106,22 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
*e = 0
|
||||
var ve Enablement
|
||||
if v.Wayland {
|
||||
*e |= EWayland
|
||||
ve |= EWayland
|
||||
}
|
||||
if v.X11 {
|
||||
*e |= EX11
|
||||
ve |= EX11
|
||||
}
|
||||
if v.DBus {
|
||||
*e |= EDBus
|
||||
ve |= EDBus
|
||||
}
|
||||
if v.PipeWire {
|
||||
*e |= EPipeWire
|
||||
ve |= EPipeWire
|
||||
}
|
||||
if v.Pulse {
|
||||
*e |= EPulse
|
||||
ve |= EPulse
|
||||
}
|
||||
*e = Enablements(ve)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestEnablementString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
flags hst.Enablements
|
||||
flags hst.Enablement
|
||||
want string
|
||||
}{
|
||||
{0, "(no enablements)"},
|
||||
@@ -59,13 +59,13 @@ func TestEnablements(t *testing.T) {
|
||||
sData string
|
||||
}{
|
||||
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
||||
{"zero", new(hst.Enablements(0)), `{}`, `{"value":{},"magic":3236757504}`},
|
||||
{"wayland", new(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||
{"x11", new(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||
{"dbus", new(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||
{"pipewire", new(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
||||
{"pulse", new(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||
{"all", new(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
||||
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
||||
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||
{"pipewire", hst.NewEnablements(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
||||
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||
{"all", hst.NewEnablements(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -137,7 +137,7 @@ func TestEnablements(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("val", func(t *testing.T) {
|
||||
if got := new(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||
if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||
t.Errorf("Unwrap: %v", got)
|
||||
}
|
||||
})
|
||||
@@ -146,6 +146,9 @@ func TestEnablements(t *testing.T) {
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := (*hst.Enablements)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("MarshalJSON: error = %v", err)
|
||||
}
|
||||
if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func Template() *Config {
|
||||
return &Config{
|
||||
ID: "org.chromium.Chromium",
|
||||
|
||||
Enablements: new(EWayland | EDBus | EPipeWire),
|
||||
Enablements: NewEnablements(EWayland | EDBus | EPipeWire),
|
||||
|
||||
SessionBus: &BusConfig{
|
||||
See: nil,
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package landlock_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/landlock"
|
||||
)
|
||||
|
||||
func TestLandlockString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
rulesetAttr *landlock.RulesetAttr
|
||||
want string
|
||||
}{
|
||||
{"nil", nil, "NULL"},
|
||||
{"zero", new(landlock.RulesetAttr), "0"},
|
||||
{"some", &landlock.RulesetAttr{Scoped: landlock.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
||||
{"set", &landlock.RulesetAttr{
|
||||
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_MAKE_SYM | landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV | landlock.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
||||
{"all", &landlock.RulesetAttr{
|
||||
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
landlock.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||
landlock.LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
landlock.LANDLOCK_ACCESS_FS_READ_DIR |
|
||||
landlock.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||
landlock.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||
landlock.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||
landlock.LANDLOCK_ACCESS_FS_REFER |
|
||||
landlock.LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
landlock.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||
landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.rulesetAttr.String(); got != tc.want {
|
||||
t.Errorf("String: %s, want %s", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLandlockAttrSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := 24
|
||||
if got := unsafe.Sizeof(landlock.RulesetAttr{}); got != uintptr(want) {
|
||||
t.Errorf("Sizeof: %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,7 @@ type outcome struct {
|
||||
syscallDispatcher
|
||||
}
|
||||
|
||||
// finalise prepares an outcome for main.
|
||||
func (k *outcome) finalise(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
id *hst.ID,
|
||||
config *hst.Config,
|
||||
flags int,
|
||||
) error {
|
||||
func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *hst.ID, config *hst.Config) error {
|
||||
if ctx == nil || id == nil {
|
||||
// unreachable
|
||||
panic("invalid call to finalise")
|
||||
@@ -50,7 +43,7 @@ func (k *outcome) finalise(
|
||||
}
|
||||
k.ctx = ctx
|
||||
|
||||
if err := config.Validate(flags); err != nil {
|
||||
if err := config.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ type outcomeStateSys struct {
|
||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||
appId string
|
||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||
et hst.Enablements
|
||||
et hst.Enablement
|
||||
|
||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||
directWayland bool
|
||||
|
||||
@@ -297,12 +297,12 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
||||
// accumulate enablements of remaining instances
|
||||
var (
|
||||
// alive enablement bits
|
||||
rt hst.Enablements
|
||||
rt hst.Enablement
|
||||
// alive instance count
|
||||
n int
|
||||
)
|
||||
for eh := range entries {
|
||||
var et hst.Enablements
|
||||
var et hst.Enablement
|
||||
if et, err = eh.Load(nil); err != nil {
|
||||
perror(err, "read state header of instance "+eh.ID.String())
|
||||
} else {
|
||||
|
||||
@@ -18,13 +18,7 @@ import (
|
||||
func IsPollDescriptor(fd uintptr) bool
|
||||
|
||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||
func Main(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
config *hst.Config,
|
||||
flags int,
|
||||
fd int,
|
||||
) {
|
||||
func Main(ctx context.Context, msg message.Msg, config *hst.Config, fd int) {
|
||||
// avoids runtime internals or standard streams
|
||||
if fd >= 0 {
|
||||
if IsPollDescriptor(uintptr(fd)) || fd < 3 {
|
||||
@@ -40,7 +34,7 @@ func Main(
|
||||
k := outcome{syscallDispatcher: direct{msg}}
|
||||
|
||||
finaliseTime := time.Now()
|
||||
if err := k.finalise(ctx, msg, &id, config, flags); err != nil {
|
||||
if err := k.finalise(ctx, msg, &id, config); err != nil {
|
||||
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ func TestOutcomeRun(t *testing.T) {
|
||||
},
|
||||
Filter: true,
|
||||
},
|
||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
@@ -427,7 +427,7 @@ func TestOutcomeRun(t *testing.T) {
|
||||
DirectPipeWire: true,
|
||||
|
||||
ID: "org.chromium.Chromium",
|
||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||
Container: &hst.ContainerConfig{
|
||||
Env: nil,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestSpPulseOp(t *testing.T) {
|
||||
newConfig := func() *hst.Config {
|
||||
config := hst.Template()
|
||||
config.DirectPulse = true
|
||||
config.Enablements = new(hst.EPulse)
|
||||
config.Enablements = hst.NewEnablements(hst.EPulse)
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,6 @@ import (
|
||||
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
||||
var AbsWork = fhs.AbsRoot.Append("work/")
|
||||
|
||||
// EnvJobs is the name of the environment variable holding a decimal
|
||||
// representation of the preferred job count. Its value must not affect cure
|
||||
// outcome.
|
||||
const EnvJobs = "CURE_JOBS"
|
||||
|
||||
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
||||
// it available at under in the container.
|
||||
type ExecPath struct {
|
||||
@@ -402,7 +397,7 @@ const SeccompPresets = std.PresetStrict &
|
||||
func (a *execArtifact) makeContainer(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
flags, jobs int,
|
||||
flags int,
|
||||
hostNet bool,
|
||||
temp, work *check.Absolute,
|
||||
getArtifact GetArtifactFunc,
|
||||
@@ -437,8 +432,8 @@ func (a *execArtifact) makeContainer(
|
||||
z.Hostname = "cure-net"
|
||||
}
|
||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||
z.Dir, z.Path, z.Args = a.dir, a.path, a.args
|
||||
z.Env = slices.Concat(a.env, []string{EnvJobs + "=" + strconv.Itoa(jobs)})
|
||||
|
||||
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
||||
z.Grow(len(a.paths) + 4)
|
||||
|
||||
for i, b := range a.paths {
|
||||
@@ -568,7 +563,6 @@ func (c *Cache) EnterExec(
|
||||
z, err = e.makeContainer(
|
||||
ctx, c.msg,
|
||||
c.flags,
|
||||
c.jobs,
|
||||
hostNet,
|
||||
temp, work,
|
||||
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||
@@ -608,7 +602,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||
msg := f.GetMessage()
|
||||
var z *container.Container
|
||||
if z, err = a.makeContainer(
|
||||
ctx, msg, f.cache.flags, f.GetJobs(), hostNet,
|
||||
ctx, msg, f.cache.flags, hostNet,
|
||||
f.GetTempDir(), f.GetWorkDir(),
|
||||
f.GetArtifact,
|
||||
f.cache.Ident,
|
||||
|
||||
@@ -3,6 +3,7 @@ package pkg
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
"io"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unique"
|
||||
"unsafe"
|
||||
@@ -39,45 +39,22 @@ func panicToError(errP *error) {
|
||||
}
|
||||
}
|
||||
|
||||
// irCache implements [IRCache].
|
||||
type irCache struct {
|
||||
// Artifact to [unique.Handle] of identifier cache.
|
||||
artifact sync.Map
|
||||
// Identifier free list, must not be accessed directly.
|
||||
identPool sync.Pool
|
||||
}
|
||||
|
||||
// zeroIRCache returns the initialised value of irCache.
|
||||
func zeroIRCache() irCache {
|
||||
return irCache{
|
||||
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
||||
}
|
||||
}
|
||||
|
||||
// IRCache provides memory management and caching primitives for IR and
|
||||
// identifier operations against [Artifact] implementations.
|
||||
//
|
||||
// The zero value is not safe for use.
|
||||
type IRCache struct{ irCache }
|
||||
|
||||
// NewIR returns the address of a new [IRCache].
|
||||
func NewIR() *IRCache {
|
||||
return &IRCache{zeroIRCache()}
|
||||
}
|
||||
|
||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||
//
|
||||
// IContext is valid until [Artifact.Params] returns.
|
||||
type IContext struct {
|
||||
// Address of underlying irCache, should be zeroed or made unusable after
|
||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||
// [Artifact.Params] returns and must not be exposed directly.
|
||||
ic *irCache
|
||||
cache *Cache
|
||||
// Written to by various methods, should be zeroed after [Artifact.Params]
|
||||
// returns and must not be exposed directly.
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying [context.Context].
|
||||
func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
|
||||
|
||||
// irZero is a zero IR word.
|
||||
var irZero [wordSize]byte
|
||||
|
||||
@@ -159,11 +136,11 @@ func (i *IContext) mustWrite(p []byte) {
|
||||
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
||||
// [Artifact.Dependencies].
|
||||
func (i *IContext) WriteIdent(a Artifact) {
|
||||
buf := i.ic.getIdentBuf()
|
||||
defer i.ic.putIdentBuf(buf)
|
||||
buf := i.cache.getIdentBuf()
|
||||
defer i.cache.putIdentBuf(buf)
|
||||
|
||||
IRKindIdent.encodeHeader(0).put(buf[:])
|
||||
*(*ID)(buf[wordSize:]) = i.ic.Ident(a).Value()
|
||||
*(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
|
||||
i.mustWrite(buf[:])
|
||||
}
|
||||
|
||||
@@ -206,19 +183,19 @@ func (i *IContext) WriteString(s string) {
|
||||
|
||||
// Encode writes a deterministic, efficient representation of a to w and returns
|
||||
// the first non-nil error encountered while writing to w.
|
||||
func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||
func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
||||
deps := a.Dependencies()
|
||||
idents := make([]*extIdent, len(deps))
|
||||
for i, d := range deps {
|
||||
dbuf, did := ic.unsafeIdent(d, true)
|
||||
dbuf, did := c.unsafeIdent(d, true)
|
||||
if dbuf == nil {
|
||||
dbuf = ic.getIdentBuf()
|
||||
dbuf = c.getIdentBuf()
|
||||
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
||||
*(*ID)(dbuf[wordSize:]) = did.Value()
|
||||
} else {
|
||||
ic.storeIdent(d, dbuf)
|
||||
c.storeIdent(d, dbuf)
|
||||
}
|
||||
defer ic.putIdentBuf(dbuf)
|
||||
defer c.putIdentBuf(dbuf)
|
||||
idents[i] = dbuf
|
||||
}
|
||||
slices.SortFunc(idents, func(a, b *extIdent) int {
|
||||
@@ -244,10 +221,10 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||
}
|
||||
|
||||
func() {
|
||||
i := IContext{ic, w}
|
||||
i := IContext{c, w}
|
||||
|
||||
defer panicToError(&err)
|
||||
defer func() { i.ic, i.w = nil, nil }()
|
||||
defer func() { i.cache, i.w = nil, nil }()
|
||||
|
||||
a.Params(&i)
|
||||
}()
|
||||
@@ -256,7 +233,7 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||
}
|
||||
|
||||
var f IREndFlag
|
||||
kcBuf := ic.getIdentBuf()
|
||||
kcBuf := c.getIdentBuf()
|
||||
sz := wordSize
|
||||
if kc, ok := a.(KnownChecksum); ok {
|
||||
f |= IREndKnownChecksum
|
||||
@@ -266,13 +243,13 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
||||
|
||||
_, err = w.Write(kcBuf[:sz])
|
||||
ic.putIdentBuf(kcBuf)
|
||||
c.putIdentBuf(kcBuf)
|
||||
return
|
||||
}
|
||||
|
||||
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
||||
// performs deduplication by value via the encoded map.
|
||||
func (ic *irCache) encodeAll(
|
||||
func (c *Cache) encodeAll(
|
||||
w io.Writer,
|
||||
a Artifact,
|
||||
encoded map[Artifact]struct{},
|
||||
@@ -282,13 +259,13 @@ func (ic *irCache) encodeAll(
|
||||
}
|
||||
|
||||
for _, d := range a.Dependencies() {
|
||||
if err = ic.encodeAll(w, d, encoded); err != nil {
|
||||
if err = c.encodeAll(w, d, encoded); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
encoded[a] = struct{}{}
|
||||
return ic.Encode(w, a)
|
||||
return c.Encode(w, a)
|
||||
}
|
||||
|
||||
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
||||
@@ -306,8 +283,8 @@ func (ic *irCache) encodeAll(
|
||||
// the ident cache, nor does it contribute identifiers it computes back to the
|
||||
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
||||
// similar cost and does not amortise when combined with a call to Cure.
|
||||
func (ic *irCache) EncodeAll(w io.Writer, a Artifact) error {
|
||||
return ic.encodeAll(w, a, make(map[Artifact]struct{}))
|
||||
func (c *Cache) EncodeAll(w io.Writer, a Artifact) error {
|
||||
return c.encodeAll(w, a, make(map[Artifact]struct{}))
|
||||
}
|
||||
|
||||
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"unique"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
@@ -32,14 +33,20 @@ func TestHTTPGet(t *testing.T) {
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
r := newRContext(t, c)
|
||||
var r pkg.RContext
|
||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||
reflect.NewAt(
|
||||
rCacheVal.Type(),
|
||||
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
||||
).Elem().Set(reflect.ValueOf(c))
|
||||
|
||||
f := pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
testdataChecksum.Value(),
|
||||
)
|
||||
var got []byte
|
||||
if rc, err := f.Cure(r); err != nil {
|
||||
if rc, err := f.Cure(&r); err != nil {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
} else if got, err = io.ReadAll(rc); err != nil {
|
||||
t.Fatalf("ReadAll: error = %v", err)
|
||||
@@ -58,7 +65,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
wantErrMismatch := &pkg.ChecksumMismatchError{
|
||||
Got: testdataChecksum.Value(),
|
||||
}
|
||||
if rc, err := f.Cure(r); err != nil {
|
||||
if rc, err := f.Cure(&r); err != nil {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
} else if got, err = io.ReadAll(rc); err != nil {
|
||||
t.Fatalf("ReadAll: error = %v", err)
|
||||
@@ -69,7 +76,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
}
|
||||
|
||||
// check fallback validation
|
||||
if rc, err := f.Cure(r); err != nil {
|
||||
if rc, err := f.Cure(&r); err != nil {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
|
||||
t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
|
||||
@@ -82,13 +89,18 @@ func TestHTTPGet(t *testing.T) {
|
||||
pkg.Checksum{},
|
||||
)
|
||||
wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
|
||||
if _, err := f.Cure(r); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||
if _, err := f.Cure(&r); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||
t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
|
||||
}
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
r := newRContext(t, c)
|
||||
var r pkg.RContext
|
||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||
reflect.NewAt(
|
||||
rCacheVal.Type(),
|
||||
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
||||
).Elem().Set(reflect.ValueOf(c))
|
||||
|
||||
f := pkg.NewHTTPGet(
|
||||
&client,
|
||||
@@ -108,7 +120,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
}
|
||||
|
||||
var got []byte
|
||||
if rc, err := f.Cure(r); err != nil {
|
||||
if rc, err := f.Cure(&r); err != nil {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
} else if got, err = io.ReadAll(rc); err != nil {
|
||||
t.Fatalf("ReadAll: error = %v", err)
|
||||
@@ -124,7 +136,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
"file:///testdata",
|
||||
testdataChecksum.Value(),
|
||||
)
|
||||
if rc, err := f.Cure(r); err != nil {
|
||||
if rc, err := f.Cure(&r); err != nil {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
} else if got, err = io.ReadAll(rc); err != nil {
|
||||
t.Fatalf("ReadAll: error = %v", err)
|
||||
|
||||
@@ -72,10 +72,6 @@ func MustDecode(s string) (checksum Checksum) {
|
||||
|
||||
// common holds elements and receives methods shared between different contexts.
|
||||
type common struct {
|
||||
// Context specific to this [Artifact]. The toplevel context in [Cache] must
|
||||
// not be exposed directly.
|
||||
ctx context.Context
|
||||
|
||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||
// Cure returns and must not be exposed directly.
|
||||
cache *Cache
|
||||
@@ -187,15 +183,11 @@ func (t *TContext) destroy(errP *error) {
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying [context.Context].
|
||||
func (c *common) Unwrap() context.Context { return c.ctx }
|
||||
func (c *common) Unwrap() context.Context { return c.cache.ctx }
|
||||
|
||||
// GetMessage returns [message.Msg] held by the underlying [Cache].
|
||||
func (c *common) GetMessage() message.Msg { return c.cache.msg }
|
||||
|
||||
// GetJobs returns the preferred number of jobs to run, when applicable. Its
|
||||
// value must not affect cure outcome.
|
||||
func (c *common) GetJobs() int { return c.cache.jobs }
|
||||
|
||||
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to
|
||||
// write its output to. This is not the final resting place of the [Artifact]
|
||||
// and this pathname should not be directly referred to in the final contents.
|
||||
@@ -215,11 +207,11 @@ func (t *TContext) GetTempDir() *check.Absolute { return t.temp }
|
||||
// [ChecksumMismatchError], or the underlying implementation may block on Close.
|
||||
func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
|
||||
if f, ok := a.(FileArtifact); ok {
|
||||
return c.cache.openFile(c.ctx, f)
|
||||
return c.cache.openFile(f)
|
||||
}
|
||||
|
||||
var pathname *check.Absolute
|
||||
if pathname, _, err = c.cache.cure(a, true); err != nil {
|
||||
if pathname, _, err = c.cache.Cure(a); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -380,9 +372,6 @@ type KnownChecksum interface {
|
||||
}
|
||||
|
||||
// FileArtifact refers to an [Artifact] backed by a single file.
|
||||
//
|
||||
// FileArtifact does not support fine-grained cancellation. Its context is
|
||||
// inherited from the first [TrivialArtifact] or [FloodArtifact] that opens it.
|
||||
type FileArtifact interface {
|
||||
// Cure returns [io.ReadCloser] of the full contents of [FileArtifact]. If
|
||||
// [FileArtifact] implements [KnownChecksum], Cure is responsible for
|
||||
@@ -542,30 +531,6 @@ const (
|
||||
CHostAbstract
|
||||
)
|
||||
|
||||
// toplevel holds [context.WithCancel] over caller-supplied context, where all
|
||||
// [Artifact] context are derived from.
|
||||
type toplevel struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// newToplevel returns the address of a new toplevel via ctx.
|
||||
func newToplevel(ctx context.Context) *toplevel {
|
||||
var t toplevel
|
||||
t.ctx, t.cancel = context.WithCancel(ctx)
|
||||
return &t
|
||||
}
|
||||
|
||||
// pendingCure provides synchronisation and cancellation for pending cures.
|
||||
type pendingCure struct {
|
||||
// Closed on cure completion.
|
||||
done <-chan struct{}
|
||||
// Error outcome, safe to access after done is closed.
|
||||
err error
|
||||
// Cancels the corresponding cure.
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// Cache is a support layer that implementations of [Artifact] can use to store
|
||||
// cured [Artifact] data in a content addressed fashion.
|
||||
type Cache struct {
|
||||
@@ -573,10 +538,11 @@ type Cache struct {
|
||||
// implementation and receives an equal amount of elements after.
|
||||
cures chan struct{}
|
||||
|
||||
// Parent context which toplevel was derived from.
|
||||
parent context.Context
|
||||
// For deriving curing context, must not be accessed directly.
|
||||
toplevel atomic.Pointer[toplevel]
|
||||
// [context.WithCancel] over caller-supplied context, used by [Artifact] and
|
||||
// all dependency curing goroutines.
|
||||
ctx context.Context
|
||||
// Cancels ctx.
|
||||
cancel context.CancelFunc
|
||||
// For waiting on dependency curing goroutines.
|
||||
wg sync.WaitGroup
|
||||
// Reports new cures and passed to [Artifact].
|
||||
@@ -586,11 +552,11 @@ type Cache struct {
|
||||
base *check.Absolute
|
||||
// Immutable cure options set by [Open].
|
||||
flags int
|
||||
// Immutable job count, when applicable.
|
||||
jobs int
|
||||
|
||||
// Must not be exposed directly.
|
||||
irCache
|
||||
// Artifact to [unique.Handle] of identifier cache.
|
||||
artifact sync.Map
|
||||
// Identifier free list, must not be accessed directly.
|
||||
identPool sync.Pool
|
||||
|
||||
// Synchronises access to dirChecksum.
|
||||
checksumMu sync.RWMutex
|
||||
@@ -600,11 +566,9 @@ type Cache struct {
|
||||
// Identifier to error pair for unrecoverably faulted [Artifact].
|
||||
identErr map[unique.Handle[ID]]error
|
||||
// Pending identifiers, accessed through Cure for entries not in ident.
|
||||
identPending map[unique.Handle[ID]]*pendingCure
|
||||
identPending map[unique.Handle[ID]]<-chan struct{}
|
||||
// Synchronises access to ident and corresponding filesystem entries.
|
||||
identMu sync.RWMutex
|
||||
// Synchronises entry into Abort and Cure.
|
||||
abortMu sync.RWMutex
|
||||
|
||||
// Synchronises entry into exclusive artifacts for the cure method.
|
||||
exclMu sync.Mutex
|
||||
@@ -613,10 +577,8 @@ type Cache struct {
|
||||
|
||||
// Unlocks the on-filesystem cache. Must only be called from Close.
|
||||
unlock func()
|
||||
// Whether [Cache] is considered closed.
|
||||
closed bool
|
||||
// Synchronises calls to Abort and Close.
|
||||
closeMu sync.Mutex
|
||||
// Synchronises calls to Close.
|
||||
closeOnce sync.Once
|
||||
|
||||
// Whether EnterExec has not yet returned.
|
||||
inExec atomic.Bool
|
||||
@@ -626,24 +588,24 @@ type Cache struct {
|
||||
type extIdent [wordSize + len(ID{})]byte
|
||||
|
||||
// getIdentBuf returns the address of an extIdent for Ident.
|
||||
func (ic *irCache) getIdentBuf() *extIdent { return ic.identPool.Get().(*extIdent) }
|
||||
func (c *Cache) getIdentBuf() *extIdent { return c.identPool.Get().(*extIdent) }
|
||||
|
||||
// putIdentBuf adds buf to identPool.
|
||||
func (ic *irCache) putIdentBuf(buf *extIdent) { ic.identPool.Put(buf) }
|
||||
func (c *Cache) putIdentBuf(buf *extIdent) { c.identPool.Put(buf) }
|
||||
|
||||
// storeIdent adds an [Artifact] to the artifact cache.
|
||||
func (ic *irCache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
||||
func (c *Cache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
||||
idu := unique.Make(ID(buf[wordSize:]))
|
||||
ic.artifact.Store(a, idu)
|
||||
c.artifact.Store(a, idu)
|
||||
return idu
|
||||
}
|
||||
|
||||
// Ident returns the identifier of an [Artifact].
|
||||
func (ic *irCache) Ident(a Artifact) unique.Handle[ID] {
|
||||
buf, idu := ic.unsafeIdent(a, false)
|
||||
func (c *Cache) Ident(a Artifact) unique.Handle[ID] {
|
||||
buf, idu := c.unsafeIdent(a, false)
|
||||
if buf != nil {
|
||||
idu = ic.storeIdent(a, buf)
|
||||
ic.putIdentBuf(buf)
|
||||
idu = c.storeIdent(a, buf)
|
||||
c.putIdentBuf(buf)
|
||||
}
|
||||
return idu
|
||||
}
|
||||
@@ -651,17 +613,17 @@ func (ic *irCache) Ident(a Artifact) unique.Handle[ID] {
|
||||
// unsafeIdent implements Ident but returns the underlying buffer for a newly
|
||||
// computed identifier. Callers must return this buffer to identPool. encodeKind
|
||||
// is only a hint, kind may still be encoded in the buffer.
|
||||
func (ic *irCache) unsafeIdent(a Artifact, encodeKind bool) (
|
||||
func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
|
||||
buf *extIdent,
|
||||
idu unique.Handle[ID],
|
||||
) {
|
||||
if id, ok := ic.artifact.Load(a); ok {
|
||||
if id, ok := c.artifact.Load(a); ok {
|
||||
idu = id.(unique.Handle[ID])
|
||||
return
|
||||
}
|
||||
|
||||
if ki, ok := a.(KnownIdent); ok {
|
||||
buf = ic.getIdentBuf()
|
||||
buf = c.getIdentBuf()
|
||||
if encodeKind {
|
||||
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
||||
}
|
||||
@@ -669,9 +631,9 @@ func (ic *irCache) unsafeIdent(a Artifact, encodeKind bool) (
|
||||
return
|
||||
}
|
||||
|
||||
buf = ic.getIdentBuf()
|
||||
buf = c.getIdentBuf()
|
||||
h := sha512.New384()
|
||||
if err := ic.Encode(h, a); err != nil {
|
||||
if err := c.Encode(h, a); err != nil {
|
||||
// unreachable
|
||||
panic(err)
|
||||
}
|
||||
@@ -1040,11 +1002,7 @@ func (c *Cache) Scrub(checks int) error {
|
||||
// loadOrStoreIdent attempts to load a cached [Artifact] by its identifier or
|
||||
// wait for a pending [Artifact] to cure. If neither is possible, the current
|
||||
// identifier is stored in identPending and a non-nil channel is returned.
|
||||
//
|
||||
// Since identErr is treated as grow-only, loadOrStoreIdent must not be entered
|
||||
// without holding a read lock on abortMu.
|
||||
func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) (
|
||||
ctx context.Context,
|
||||
done chan<- struct{},
|
||||
checksum unique.Handle[Checksum],
|
||||
err error,
|
||||
@@ -1061,23 +1019,20 @@ func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) (
|
||||
return
|
||||
}
|
||||
|
||||
var pending *pendingCure
|
||||
if pending, ok = c.identPending[id]; ok {
|
||||
var notify <-chan struct{}
|
||||
if notify, ok = c.identPending[id]; ok {
|
||||
c.identMu.Unlock()
|
||||
<-pending.done
|
||||
<-notify
|
||||
c.identMu.RLock()
|
||||
if checksum, ok = c.ident[id]; !ok {
|
||||
err = pending.err
|
||||
err = c.identErr[id]
|
||||
}
|
||||
c.identMu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
d := make(chan struct{})
|
||||
pending = &pendingCure{done: d}
|
||||
ctx, pending.cancel = context.WithCancel(c.toplevel.Load().ctx)
|
||||
c.wg.Add(1)
|
||||
c.identPending[id] = pending
|
||||
c.identPending[id] = d
|
||||
c.identMu.Unlock()
|
||||
done = d
|
||||
return
|
||||
@@ -1093,62 +1048,21 @@ func (c *Cache) finaliseIdent(
|
||||
) {
|
||||
c.identMu.Lock()
|
||||
if err != nil {
|
||||
c.identPending[id].err = err
|
||||
c.identErr[id] = err
|
||||
} else {
|
||||
c.ident[id] = checksum
|
||||
}
|
||||
delete(c.identPending, id)
|
||||
c.identMu.Unlock()
|
||||
c.wg.Done()
|
||||
|
||||
close(done)
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when the ongoing cure of an [Artifact]
|
||||
// referred to by the specified identifier completes. Done may return nil if
|
||||
// no ongoing cure of the specified identifier exists.
|
||||
func (c *Cache) Done(id unique.Handle[ID]) <-chan struct{} {
|
||||
c.identMu.RLock()
|
||||
pending, ok := c.identPending[id]
|
||||
c.identMu.RUnlock()
|
||||
if !ok || pending == nil {
|
||||
return nil
|
||||
}
|
||||
return pending.done
|
||||
}
|
||||
|
||||
// Cancel cancels the ongoing cure of an [Artifact] referred to by the specified
|
||||
// identifier. Cancel returns whether the [context.CancelFunc] has been killed.
|
||||
// Cancel returns after the cure is complete.
|
||||
func (c *Cache) Cancel(id unique.Handle[ID]) bool {
|
||||
c.identMu.RLock()
|
||||
pending, ok := c.identPending[id]
|
||||
c.identMu.RUnlock()
|
||||
if !ok || pending == nil || pending.cancel == nil {
|
||||
return false
|
||||
}
|
||||
pending.cancel()
|
||||
<-pending.done
|
||||
|
||||
c.abortMu.Lock()
|
||||
c.identMu.Lock()
|
||||
delete(c.identErr, id)
|
||||
c.identMu.Unlock()
|
||||
c.abortMu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// openFile tries to load [FileArtifact] from [Cache], and if that fails,
|
||||
// obtains it via [FileArtifact.Cure] instead. Notably, it does not cure
|
||||
// [FileArtifact] to the filesystem. If err is nil, the caller is responsible
|
||||
// for closing the resulting [io.ReadCloser].
|
||||
//
|
||||
// The context must originate from loadOrStoreIdent to enable cancellation.
|
||||
func (c *Cache) openFile(
|
||||
ctx context.Context,
|
||||
f FileArtifact,
|
||||
) (r io.ReadCloser, err error) {
|
||||
func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
|
||||
if kc, ok := f.(KnownChecksum); c.flags&CAssumeChecksum != 0 && ok {
|
||||
c.checksumMu.RLock()
|
||||
r, err = os.Open(c.base.Append(
|
||||
@@ -1179,7 +1093,7 @@ func (c *Cache) openFile(
|
||||
}
|
||||
}()
|
||||
}
|
||||
return f.Cure(&RContext{common{ctx, c}})
|
||||
return f.Cure(&RContext{common{c}})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1327,11 +1241,12 @@ func (c *Cache) Cure(a Artifact) (
|
||||
checksum unique.Handle[Checksum],
|
||||
err error,
|
||||
) {
|
||||
c.abortMu.RLock()
|
||||
defer c.abortMu.RUnlock()
|
||||
|
||||
if err = c.toplevel.Load().ctx.Err(); err != nil {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
err = c.ctx.Err()
|
||||
return
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
return c.cure(a, true)
|
||||
@@ -1417,16 +1332,15 @@ func (c *Cache) enterCure(a Artifact, curesExempt bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := c.toplevel.Load().ctx
|
||||
select {
|
||||
case c.cures <- struct{}{}:
|
||||
return nil
|
||||
|
||||
case <-ctx.Done():
|
||||
case <-c.ctx.Done():
|
||||
if a.IsExclusive() {
|
||||
c.exclMu.Unlock()
|
||||
}
|
||||
return ctx.Err()
|
||||
return c.ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1524,8 +1438,7 @@ func (r *RContext) NewMeasuredReader(
|
||||
return r.cache.newMeasuredReader(rc, checksum)
|
||||
}
|
||||
|
||||
// cure implements Cure without acquiring a read lock on abortMu. cure must not
|
||||
// be entered during Abort.
|
||||
// cure implements Cure without checking the full dependency graph.
|
||||
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
pathname *check.Absolute,
|
||||
checksum unique.Handle[Checksum],
|
||||
@@ -1544,11 +1457,8 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
done chan<- struct{}
|
||||
)
|
||||
ctx, done, checksum, err = c.loadOrStoreIdent(id)
|
||||
var done chan<- struct{}
|
||||
done, checksum, err = c.loadOrStoreIdent(id)
|
||||
if done == nil {
|
||||
return
|
||||
} else {
|
||||
@@ -1661,7 +1571,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
if err = c.enterCure(a, curesExempt); err != nil {
|
||||
return
|
||||
}
|
||||
r, err = f.Cure(&RContext{common{ctx, c}})
|
||||
r, err = f.Cure(&RContext{common{c}})
|
||||
if err == nil {
|
||||
if checksumPathname == nil || c.flags&CValidateKnown != 0 {
|
||||
h := sha512.New384()
|
||||
@@ -1741,7 +1651,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
c.base.Append(dirWork, ids),
|
||||
c.base.Append(dirTemp, ids),
|
||||
ids, nil, nil, nil,
|
||||
common{ctx, c},
|
||||
common{c},
|
||||
}
|
||||
switch ca := a.(type) {
|
||||
case TrivialArtifact:
|
||||
@@ -1892,42 +1802,14 @@ func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Abort cancels all pending cures and waits for them to clean up, but does not
|
||||
// close the cache.
|
||||
func (c *Cache) Abort() {
|
||||
c.closeMu.Lock()
|
||||
defer c.closeMu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
c.toplevel.Load().cancel()
|
||||
c.abortMu.Lock()
|
||||
defer c.abortMu.Unlock()
|
||||
|
||||
// holding abortMu, identPending stays empty
|
||||
c.wg.Wait()
|
||||
c.identMu.Lock()
|
||||
c.toplevel.Store(newToplevel(c.parent))
|
||||
clear(c.identErr)
|
||||
c.identMu.Unlock()
|
||||
}
|
||||
|
||||
// Close cancels all pending cures and waits for them to clean up.
|
||||
func (c *Cache) Close() {
|
||||
c.closeMu.Lock()
|
||||
defer c.closeMu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
c.closed = true
|
||||
c.toplevel.Load().cancel()
|
||||
c.wg.Wait()
|
||||
close(c.cures)
|
||||
c.unlock()
|
||||
c.closeOnce.Do(func() {
|
||||
c.cancel()
|
||||
c.wg.Wait()
|
||||
close(c.cures)
|
||||
c.unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// Open returns the address of a newly opened instance of [Cache].
|
||||
@@ -1936,7 +1818,7 @@ func (c *Cache) Close() {
|
||||
// caller-supplied value, however direct calls to [Cache.Cure] is not subject
|
||||
// to this limitation.
|
||||
//
|
||||
// A cures or jobs value of 0 or lower is equivalent to the value returned by
|
||||
// A cures value of 0 or lower is equivalent to the value returned by
|
||||
// [runtime.NumCPU].
|
||||
//
|
||||
// A successful call to Open guarantees exclusive access to the on-filesystem
|
||||
@@ -1946,10 +1828,10 @@ func (c *Cache) Close() {
|
||||
func Open(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
flags, cures, jobs int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
) (*Cache, error) {
|
||||
return open(ctx, msg, flags, cures, jobs, base, true)
|
||||
return open(ctx, msg, flags, cures, base, true)
|
||||
}
|
||||
|
||||
// open implements Open but allows omitting the [lockedfile] lock when called
|
||||
@@ -1957,16 +1839,13 @@ func Open(
|
||||
func open(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
flags, cures, jobs int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
lock bool,
|
||||
) (*Cache, error) {
|
||||
if cures < 1 {
|
||||
cures = runtime.NumCPU()
|
||||
}
|
||||
if jobs < 1 {
|
||||
jobs = runtime.NumCPU()
|
||||
}
|
||||
|
||||
for _, name := range []string{
|
||||
dirIdentifier,
|
||||
@@ -1981,25 +1860,22 @@ func open(
|
||||
}
|
||||
|
||||
c := Cache{
|
||||
parent: ctx,
|
||||
|
||||
cures: make(chan struct{}, cures),
|
||||
flags: flags,
|
||||
jobs: jobs,
|
||||
|
||||
msg: msg,
|
||||
base: base,
|
||||
|
||||
irCache: zeroIRCache(),
|
||||
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
||||
|
||||
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
||||
identErr: make(map[unique.Handle[ID]]error),
|
||||
identPending: make(map[unique.Handle[ID]]*pendingCure),
|
||||
identPending: make(map[unique.Handle[ID]]<-chan struct{}),
|
||||
|
||||
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
|
||||
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
|
||||
}
|
||||
c.toplevel.Store(newToplevel(ctx))
|
||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||
|
||||
if lock || !testing.Testing() {
|
||||
if unlock, err := lockedfile.MutexAt(
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
@@ -26,7 +25,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/landlock"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/stub"
|
||||
"hakurei.app/message"
|
||||
@@ -36,28 +34,11 @@ import (
|
||||
func unsafeOpen(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
flags, cures, jobs int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
lock bool,
|
||||
) (*pkg.Cache, error)
|
||||
|
||||
// newRContext returns the address of a new [pkg.RContext] unsafely created for
|
||||
// the specified [testing.TB].
|
||||
func newRContext(tb testing.TB, c *pkg.Cache) *pkg.RContext {
|
||||
var r pkg.RContext
|
||||
rContextVal := reflect.ValueOf(&r).Elem().FieldByName("ctx")
|
||||
reflect.NewAt(
|
||||
rContextVal.Type(),
|
||||
unsafe.Pointer(rContextVal.UnsafeAddr()),
|
||||
).Elem().Set(reflect.ValueOf(tb.Context()))
|
||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||
reflect.NewAt(
|
||||
rCacheVal.Type(),
|
||||
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
||||
).Elem().Set(reflect.ValueOf(c))
|
||||
return &r
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); os.Exit(m.Run()) }
|
||||
|
||||
// overrideIdent overrides the ID method of [Artifact].
|
||||
@@ -248,7 +229,7 @@ func TestIdent(t *testing.T) {
|
||||
var cache *pkg.Cache
|
||||
if a, err := check.NewAbs(t.TempDir()); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache, err = pkg.Open(t.Context(), msg, 0, 0, 0, a); err != nil {
|
||||
} else if cache, err = pkg.Open(t.Context(), msg, 0, 0, a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(cache.Close)
|
||||
@@ -312,7 +293,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
flags := tc.flags
|
||||
|
||||
if info.CanDegrade {
|
||||
if _, err := landlock.GetABI(); err != nil {
|
||||
if _, err := container.LandlockGetABI(); err != nil {
|
||||
if !errors.Is(err, syscall.ENOSYS) {
|
||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||
}
|
||||
@@ -322,7 +303,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
}
|
||||
|
||||
var scrubFunc func() error // scrub after hashing
|
||||
if c, err := pkg.Open(t.Context(), msg, flags, 1<<4, 0, base); err != nil {
|
||||
if c, err := pkg.Open(t.Context(), msg, flags, 1<<4, base); err != nil {
|
||||
t.Fatalf("Open: error = %v", err)
|
||||
} else {
|
||||
t.Cleanup(c.Close)
|
||||
@@ -624,7 +605,7 @@ func TestCache(t *testing.T) {
|
||||
if c0, err := unsafeOpen(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, 0, 0, base, false,
|
||||
0, 0, base, false,
|
||||
); err != nil {
|
||||
t.Fatalf("open: error = %v", err)
|
||||
} else {
|
||||
@@ -894,69 +875,17 @@ func TestCache(t *testing.T) {
|
||||
t.Fatalf("Scrub: error = %#v, want %#v", err, wantErrScrub)
|
||||
}
|
||||
|
||||
notify := c.Done(unique.Make(pkg.ID{0xff}))
|
||||
identPendingVal := reflect.ValueOf(c).Elem().FieldByName("identPending")
|
||||
identPending := reflect.NewAt(
|
||||
identPendingVal.Type(),
|
||||
unsafe.Pointer(identPendingVal.UnsafeAddr()),
|
||||
).Elem().Interface().(map[unique.Handle[pkg.ID]]<-chan struct{})
|
||||
notify := identPending[unique.Make(pkg.ID{0xff})]
|
||||
go close(n)
|
||||
if notify != nil {
|
||||
<-notify
|
||||
}
|
||||
for c.Done(unique.Make(pkg.ID{0xff})) != nil {
|
||||
}
|
||||
<-notify
|
||||
<-wCureDone
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"cancel abort block", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
var started sync.WaitGroup
|
||||
defer started.Wait()
|
||||
|
||||
blockCures := func(d byte, e stub.UniqueError, n int) {
|
||||
started.Add(n)
|
||||
for i := range n {
|
||||
wg.Go(func() {
|
||||
if _, _, err := c.Cure(overrideIdent{pkg.ID{d, byte(i)}, &stubArtifact{
|
||||
kind: pkg.KindTar,
|
||||
cure: func(t *pkg.TContext) error {
|
||||
started.Done()
|
||||
<-t.Unwrap().Done()
|
||||
return e + stub.UniqueError(i)
|
||||
},
|
||||
}}); !reflect.DeepEqual(err, e+stub.UniqueError(i)) {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
started.Wait()
|
||||
}
|
||||
|
||||
blockCures(0xfd, 0xbad, 16)
|
||||
c.Abort()
|
||||
wg.Wait()
|
||||
|
||||
blockCures(0xfd, 0xcafe, 16)
|
||||
c.Abort()
|
||||
wg.Wait()
|
||||
|
||||
blockCures(0xff, 0xbad, 1)
|
||||
if !c.Cancel(unique.Make(pkg.ID{0xff})) {
|
||||
t.Fatal("missed cancellation")
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
blockCures(0xff, 0xcafe, 1)
|
||||
if !c.Cancel(unique.Make(pkg.ID{0xff})) {
|
||||
t.Fatal("missed cancellation")
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for c.Cancel(unique.Make(pkg.ID{0xff})) {
|
||||
}
|
||||
|
||||
c.Close()
|
||||
c.Abort()
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"no assume checksum", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
makeGarbage := func(work *check.Absolute, wantErr error) error {
|
||||
if err := os.Mkdir(work.String(), 0700); err != nil {
|
||||
@@ -1333,7 +1262,7 @@ func TestNew(t *testing.T) {
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, 0, 0, check.MustAbs(container.Nonexistent),
|
||||
0, 0, check.MustAbs(container.Nonexistent),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
@@ -1361,7 +1290,7 @@ func TestNew(t *testing.T) {
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, 0, 0, tempDir.Append("cache"),
|
||||
0, 0, tempDir.Append("cache"),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ var _ fmt.Stringer = new(tarArtifactNamed)
|
||||
func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
|
||||
|
||||
// NewTar returns a new [Artifact] backed by the supplied [Artifact] and
|
||||
// compression method. The source [Artifact] must be a [FileArtifact].
|
||||
// compression method. The source [Artifact] must be compatible with
|
||||
// [TContext.Open].
|
||||
func NewTar(a Artifact, compression uint32) Artifact {
|
||||
ta := tarArtifact{a, compression}
|
||||
if s, ok := a.(fmt.Stringer); ok {
|
||||
|
||||
12
internal/pkg/testdata/main.go
vendored
12
internal/pkg/testdata/main.go
vendored
@@ -9,9 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
@@ -23,10 +21,6 @@ func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("testtool: ")
|
||||
|
||||
environ := slices.DeleteFunc(slices.Clone(os.Environ()), func(s string) bool {
|
||||
return s == "CURE_JOBS="+strconv.Itoa(runtime.NumCPU())
|
||||
})
|
||||
|
||||
var hostNet, layers, promote bool
|
||||
if len(os.Args) == 2 && os.Args[0] == "testtool" {
|
||||
switch os.Args[1] {
|
||||
@@ -54,15 +48,15 @@ func main() {
|
||||
|
||||
var overlayRoot bool
|
||||
wantEnv := []string{"HAKUREI_TEST=1"}
|
||||
if len(environ) == 2 {
|
||||
if len(os.Environ()) == 2 {
|
||||
overlayRoot = true
|
||||
if !layers && !promote {
|
||||
log.SetPrefix("testtool(overlay root): ")
|
||||
}
|
||||
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
|
||||
}
|
||||
if !slices.Equal(wantEnv, environ) {
|
||||
log.Fatalf("Environ: %q, want %q", environ, wantEnv)
|
||||
if !slices.Equal(wantEnv, os.Environ()) {
|
||||
log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv)
|
||||
}
|
||||
|
||||
var overlayWork bool
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newAttr() (pkg.Artifact, string) {
|
||||
version = "2.5.2"
|
||||
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
||||
)
|
||||
return t.NewPackage("attr", version, newTar(
|
||||
"https://download.savannah.nongnu.org/releases/attr/"+
|
||||
return t.NewPackage("attr", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://download.savannah.nongnu.org/releases/attr/"+
|
||||
"attr-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
@@ -81,10 +81,10 @@ func (t Toolchain) newACL() (pkg.Artifact, string) {
|
||||
version = "2.3.2"
|
||||
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
||||
)
|
||||
return t.NewPackage("acl", version, newTar(
|
||||
"https://download.savannah.nongnu.org/releases/acl/"+
|
||||
return t.NewPackage("acl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://download.savannah.nongnu.org/releases/acl/"+
|
||||
"acl-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
// makes assumptions about uid_map/gid_map
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
type PArtifact int
|
||||
|
||||
const (
|
||||
CompilerRT PArtifact = iota
|
||||
LLVMCompilerRT PArtifact = iota
|
||||
LLVMRuntimes
|
||||
Clang
|
||||
LLVMClang
|
||||
|
||||
// EarlyInit is the Rosa OS init program.
|
||||
EarlyInit
|
||||
@@ -64,7 +64,6 @@ const (
|
||||
GenInitCPIO
|
||||
Gettext
|
||||
Git
|
||||
Glslang
|
||||
GnuTLS
|
||||
Go
|
||||
Gperf
|
||||
@@ -77,17 +76,13 @@ const (
|
||||
LibXau
|
||||
Libbsd
|
||||
Libcap
|
||||
Libclc
|
||||
Libdrm
|
||||
Libev
|
||||
Libexpat
|
||||
Libffi
|
||||
Libgd
|
||||
Libglvnd
|
||||
Libiconv
|
||||
Libmd
|
||||
Libmnl
|
||||
Libpciaccess
|
||||
Libnftnl
|
||||
Libpsl
|
||||
Libseccomp
|
||||
@@ -124,18 +119,15 @@ const (
|
||||
PerlTermReadKey
|
||||
PerlTextCharWidth
|
||||
PerlTextWrapI18N
|
||||
PerlUnicodeLineBreak
|
||||
PerlUnicodeGCString
|
||||
PerlYAMLTiny
|
||||
PkgConfig
|
||||
Procps
|
||||
Python
|
||||
PythonIniConfig
|
||||
PythonMako
|
||||
PythonMarkupSafe
|
||||
PythonPackaging
|
||||
PythonPluggy
|
||||
PythonPyTest
|
||||
PythonPyYAML
|
||||
PythonPygments
|
||||
QEMU
|
||||
Rdfind
|
||||
@@ -143,8 +135,6 @@ const (
|
||||
Rsync
|
||||
Sed
|
||||
Setuptools
|
||||
SPIRVHeaders
|
||||
SPIRVTools
|
||||
SquashfsTools
|
||||
Strace
|
||||
TamaGo
|
||||
@@ -158,17 +148,15 @@ const (
|
||||
WaylandProtocols
|
||||
XCB
|
||||
XCBProto
|
||||
XDGDBusProxy
|
||||
XZ
|
||||
Xproto
|
||||
XZ
|
||||
Zlib
|
||||
Zstd
|
||||
|
||||
// PresetUnexportedStart is the first unexported preset.
|
||||
PresetUnexportedStart
|
||||
|
||||
llvmSource = iota - 1
|
||||
buildcatrust
|
||||
buildcatrust = iota - 1
|
||||
utilMacros
|
||||
|
||||
// Musl is a standalone libc that does not depend on the toolchain.
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
|
||||
version = "1.3"
|
||||
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
|
||||
)
|
||||
return t.NewPackage("argp-standalone", version, newTar(
|
||||
"http://www.lysator.liu.se/~nisse/misc/"+
|
||||
return t.NewPackage("argp-standalone", version, pkg.NewHTTPGetTar(
|
||||
nil, "http://www.lysator.liu.se/~nisse/misc/"+
|
||||
"argp-standalone-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
|
||||
@@ -7,9 +7,9 @@ func (t Toolchain) newBzip2() (pkg.Artifact, string) {
|
||||
version = "1.0.8"
|
||||
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
|
||||
)
|
||||
return t.NewPackage("bzip2", version, newTar(
|
||||
"https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("bzip2", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
|
||||
@@ -13,11 +13,10 @@ func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||
version = "4.3.1"
|
||||
checksum = "RHpzZiM1kJ5bwLjo9CpXSeHJJg3hTtV9QxBYpQoYwKFtRh5YhGWpShrqZCSOzQN6"
|
||||
)
|
||||
return t.NewPackage("cmake", version, newFromGitHubRelease(
|
||||
"Kitware/CMake",
|
||||
"v"+version,
|
||||
"cmake-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
||||
"v"+version+"/cmake-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// test suite expects writable source tree
|
||||
@@ -91,7 +90,7 @@ index 2ead810437..f85cbb8b1c 100644
|
||||
ConfigureName: "/usr/src/cmake/bootstrap",
|
||||
Configure: []KV{
|
||||
{"prefix", "/system"},
|
||||
{"parallel", jobsE},
|
||||
{"parallel", `"$(nproc)"`},
|
||||
{"--"},
|
||||
{"-DCMAKE_USE_OPENSSL", "OFF"},
|
||||
{"-DCMake_TEST_NO_NETWORK", "ON"},
|
||||
@@ -119,6 +118,9 @@ func init() {
|
||||
|
||||
// CMakeHelper is the [CMake] build system helper.
|
||||
type CMakeHelper struct {
|
||||
// Joined with name with a dash if non-empty.
|
||||
Variant string
|
||||
|
||||
// Path elements joined with source.
|
||||
Append []string
|
||||
|
||||
@@ -133,6 +135,14 @@ type CMakeHelper struct {
|
||||
|
||||
var _ Helper = new(CMakeHelper)
|
||||
|
||||
// name returns its arguments and an optional variant string joined with '-'.
|
||||
func (attr *CMakeHelper) name(name, version string) string {
|
||||
if attr != nil && attr.Variant != "" {
|
||||
name += "-" + attr.Variant
|
||||
}
|
||||
return name + "-" + version
|
||||
}
|
||||
|
||||
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||
func (attr *CMakeHelper) extra(int) P {
|
||||
if attr != nil && attr.Make {
|
||||
@@ -170,8 +180,10 @@ func (attr *CMakeHelper) script(name string) string {
|
||||
}
|
||||
|
||||
generate := "Ninja"
|
||||
jobs := ""
|
||||
if attr.Make {
|
||||
generate = "'Unix Makefiles'"
|
||||
jobs += ` "--parallel=$(nproc)"`
|
||||
}
|
||||
|
||||
return `
|
||||
@@ -189,7 +201,7 @@ cmake -G ` + generate + ` \
|
||||
}), " \\\n\t") + ` \
|
||||
-DCMAKE_INSTALL_PREFIX=/system \
|
||||
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
||||
cmake --build . --parallel=` + jobsE + `
|
||||
cmake --build .` + jobs + `
|
||||
cmake --install . --prefix=/work/system
|
||||
` + attr.Script
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newConnman() (pkg.Artifact, string) {
|
||||
version = "2.0"
|
||||
checksum = "MhVTdJOhndnZn2SWd8URKo_Pj7Zvc14tntEbrVOf9L3yVWJvpb3v3Q6104tWJgtW"
|
||||
)
|
||||
return t.NewPackage("connman", version, newTar(
|
||||
"https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
||||
return t.NewPackage("connman", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
||||
"snapshot/connman-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
|
||||
@@ -7,15 +7,15 @@ func (t Toolchain) newCurl() (pkg.Artifact, string) {
|
||||
version = "8.19.0"
|
||||
checksum = "YHuVLVVp8q_Y7-JWpID5ReNjq2Zk6t7ArHB6ngQXilp_R5l3cubdxu3UKo-xDByv"
|
||||
)
|
||||
return t.NewPackage("curl", version, newTar(
|
||||
"https://curl.se/download/curl-"+version+".tar.bz2",
|
||||
checksum,
|
||||
return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://curl.se/download/curl-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// remove broken test
|
||||
Writable: true,
|
||||
ScriptEarly: `
|
||||
chmod +w tests/data && rm -f tests/data/test459
|
||||
chmod +w tests/data && rm tests/data/test459
|
||||
`,
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
@@ -25,7 +25,7 @@ chmod +w tests/data && rm -f tests/data/test459
|
||||
{"disable-smb"},
|
||||
},
|
||||
Check: []string{
|
||||
"TFLAGS=" + jobsLFlagE,
|
||||
`TFLAGS="-j$(expr "$(nproc)" '*' 2)"`,
|
||||
"test-nonflaky",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,11 +7,11 @@ func (t Toolchain) newDBus() (pkg.Artifact, string) {
|
||||
version = "1.16.2"
|
||||
checksum = "INwOuNdrDG7XW5ilW_vn8JSxEa444rRNc5ho97i84I1CNF09OmcFcV-gzbF4uCyg"
|
||||
)
|
||||
return t.NewPackage("dbus", version, newFromGitLab(
|
||||
"gitlab.freedesktop.org",
|
||||
"dbus/dbus",
|
||||
"dbus-"+version,
|
||||
checksum,
|
||||
return t.NewPackage("dbus", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gitlab.freedesktop.org/dbus/dbus/-/archive/"+
|
||||
"dbus-"+version+"/dbus-dbus-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// OSError: [Errno 30] Read-only file system: '/usr/src/dbus/subprojects/packagecache'
|
||||
Writable: true,
|
||||
@@ -44,38 +44,3 @@ func init() {
|
||||
ID: 5356,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newXDGDBusProxy() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.1.7"
|
||||
checksum = "UW5Pe-TP-XAaN-kTbxrkOQ7eYdmlAQlr2pdreLtPT0uwdAz-7rzDP8V_8PWuZBup"
|
||||
)
|
||||
return t.NewPackage("xdg-dbus-proxy", version, newFromGitHub(
|
||||
"flatpak/xdg-dbus-proxy",
|
||||
version,
|
||||
checksum,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Dman", "disabled"},
|
||||
},
|
||||
},
|
||||
DBus,
|
||||
|
||||
GLib,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[XDGDBusProxy] = Metadata{
|
||||
f: Toolchain.newXDGDBusProxy,
|
||||
|
||||
Name: "xdg-dbus-proxy",
|
||||
Description: "a filtering proxy for D-Bus connections",
|
||||
Website: "https://github.com/flatpak/xdg-dbus-proxy",
|
||||
|
||||
Dependencies: P{
|
||||
GLib,
|
||||
},
|
||||
|
||||
ID: 58434,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newDTC() (pkg.Artifact, string) {
|
||||
version = "1.7.2"
|
||||
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
|
||||
)
|
||||
return t.NewPackage("dtc", version, newTar(
|
||||
"https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
||||
return t.NewPackage("dtc", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
||||
"dtc-v"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// works around buggy test:
|
||||
|
||||
@@ -4,13 +4,13 @@ import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newElfutils() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.195"
|
||||
checksum = "JrGnBD38w8Mj0ZxDw3fKlRBFcLvRKu8rcYnX35R9yTlUSYnzTazyLboG-a2CsJlu"
|
||||
version = "0.194"
|
||||
checksum = "Q3XUygUPv9vR1TkWucwUsQ8Kb1_F6gzk-KMPELr3cC_4AcTrprhVPMvN0CKkiYRa"
|
||||
)
|
||||
return t.NewPackage("elfutils", version, newTar(
|
||||
"https://sourceware.org/elfutils/ftp/"+
|
||||
return t.NewPackage("elfutils", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://sourceware.org/elfutils/ftp/"+
|
||||
version+"/elfutils-"+version+".tar.bz2",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
|
||||
@@ -135,11 +135,10 @@ func newIANAEtc() pkg.Artifact {
|
||||
version = "20251215"
|
||||
checksum = "kvKz0gW_rGG5QaNK9ZWmWu1IEgYAdmhj_wR7DYrh3axDfIql_clGRHmelP7525NJ"
|
||||
)
|
||||
return newFromGitHubRelease(
|
||||
"Mic92/iana-etc",
|
||||
version,
|
||||
"iana-etc-"+version+".tar.gz",
|
||||
checksum,
|
||||
return pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/Mic92/iana-etc/releases/download/"+
|
||||
version+"/iana-etc-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
|
||||
version = "1.37.2"
|
||||
checksum = "4ve-eDqVspzQ6VWDhPS0NjW3aSenBJcPAJq_BFT7OOFgUdrQzoTBxZWipDAGWxF8"
|
||||
)
|
||||
return t.NewPackage("fakeroot", version, newFromGitLab(
|
||||
"salsa.debian.org",
|
||||
"clint/fakeroot",
|
||||
"upstream/"+version,
|
||||
checksum,
|
||||
return t.NewPackage("fakeroot", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://salsa.debian.org/clint/fakeroot/-/archive/upstream/"+
|
||||
version+"/fakeroot-upstream-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
|
||||
|
||||
@@ -9,11 +9,10 @@ func (t Toolchain) newFlex() (pkg.Artifact, string) {
|
||||
version = "2.6.4"
|
||||
checksum = "p9POjQU7VhgOf3x5iFro8fjhy0NOanvA7CTeuWS_veSNgCixIJshTrWVkc5XLZkB"
|
||||
)
|
||||
return t.NewPackage("flex", version, newFromGitHubRelease(
|
||||
"westes/flex",
|
||||
"v"+version,
|
||||
"flex-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("flex", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/westes/flex/releases/download/"+
|
||||
"v"+version+"/flex-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
M4,
|
||||
|
||||
@@ -7,11 +7,10 @@ func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
||||
version = "3.18.2"
|
||||
checksum = "iL-7b7eUtmlVSf5cSq0dzow3UiqSjBmzV3cI_ENPs1tXcHdktkG45j1V12h-4jZe"
|
||||
)
|
||||
return t.NewPackage("fuse", version, newFromGitHubRelease(
|
||||
"libfuse/libfuse",
|
||||
"fuse-"+version,
|
||||
"fuse-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/libfuse/libfuse/releases/download/"+
|
||||
"fuse-"+version+"/fuse-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
|
||||
@@ -12,10 +12,10 @@ func (t Toolchain) newGit() (pkg.Artifact, string) {
|
||||
version = "2.53.0"
|
||||
checksum = "rlqSTeNgSeVKJA7nvzGqddFH8q3eFEPB4qRZft-4zth8wTHnbTbm7J90kp_obHGm"
|
||||
)
|
||||
return t.NewPackage("git", version, newTar(
|
||||
"https://www.kernel.org/pub/software/scm/git/"+
|
||||
return t.NewPackage("git", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.kernel.org/pub/software/scm/git/"+
|
||||
"git-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
ScriptEarly: `
|
||||
@@ -58,7 +58,7 @@ disable_test t2200-add-update
|
||||
"prove",
|
||||
},
|
||||
Install: `make \
|
||||
` + jobsFlagE + ` \
|
||||
"-j$(nproc)" \
|
||||
DESTDIR=/work \
|
||||
NO_INSTALL_HARDLINKS=1 \
|
||||
install`,
|
||||
@@ -114,8 +114,3 @@ git \
|
||||
rm -rf /work/.git
|
||||
`, resolvconf())
|
||||
}
|
||||
|
||||
// newTagRemote is a helper around NewViaGit for a tag on a git remote.
|
||||
func (t Toolchain) newTagRemote(url, tag, checksum string) pkg.Artifact {
|
||||
return t.NewViaGit(url, "refs/tags/"+tag, mustDecode(checksum))
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newSPIRVHeaders() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.4.341.0"
|
||||
checksum = "0PL43-19Iaw4k7_D8J8BvoJ-iLgCVSYZ2ThgDPGfAJwIJFtre7l0cnQtLjcY-JvD"
|
||||
)
|
||||
return t.NewPackage("spirv-headers", version, newFromGitHub(
|
||||
"KhronosGroup/SPIRV-Headers",
|
||||
"vulkan-sdk-"+version,
|
||||
checksum,
|
||||
), nil, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
},
|
||||
}), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SPIRVHeaders] = Metadata{
|
||||
f: Toolchain.newSPIRVHeaders,
|
||||
|
||||
Name: "spirv-headers",
|
||||
Description: "machine-readable files for the SPIR-V Registry",
|
||||
Website: "https://github.com/KhronosGroup/SPIRV-Headers",
|
||||
|
||||
ID: 230542,
|
||||
|
||||
// upstream changed version scheme, anitya incapable of filtering them
|
||||
latest: func(v *Versions) string {
|
||||
for _, s := range v.Stable {
|
||||
fields := strings.SplitN(s, ".", 4)
|
||||
if len(fields) != 4 {
|
||||
continue
|
||||
}
|
||||
if slices.ContainsFunc(fields, func(f string) bool {
|
||||
return slices.ContainsFunc([]byte(f), func(d byte) bool {
|
||||
return d < '0' || d > '9'
|
||||
})
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
return s
|
||||
}
|
||||
return v.Latest
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newSPIRVTools() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2026.1"
|
||||
checksum = "ZSQPQx8NltCDzQLk4qlaVxyWRWeI_JtsjEpeFt3kezTanl9DTHfLixSUCezMFBjv"
|
||||
)
|
||||
return t.NewPackage("spirv-tools", version, newFromGitHub(
|
||||
"KhronosGroup/SPIRV-Tools",
|
||||
"v"+version,
|
||||
checksum,
|
||||
), nil, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
{"SPIRV-Headers_SOURCE_DIR", "/system"},
|
||||
},
|
||||
},
|
||||
Python,
|
||||
|
||||
SPIRVHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SPIRVTools] = Metadata{
|
||||
f: Toolchain.newSPIRVTools,
|
||||
|
||||
Name: "spirv-tools",
|
||||
Description: "an API and commands for processing SPIR-V modules",
|
||||
Website: "https://github.com/KhronosGroup/SPIRV-Tools",
|
||||
|
||||
Dependencies: P{
|
||||
SPIRVHeaders,
|
||||
},
|
||||
|
||||
ID: 14894,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newGlslang() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "16.2.0"
|
||||
checksum = "6_UuF9reLRDaVkgO-9IfB3kMwme3lQZM8LL8YsJwPdUFkrjzxJtf2A9X3w9nFxj2"
|
||||
)
|
||||
return t.NewPackage("glslang", version, newFromGitHub(
|
||||
"KhronosGroup/glslang",
|
||||
version,
|
||||
checksum,
|
||||
), &PackageAttr{
|
||||
// test suite writes to source
|
||||
Writable: true,
|
||||
Chmod: true,
|
||||
}, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
{"BUILD_SHARED_LIBS", "ON"},
|
||||
{"ALLOW_EXTERNAL_SPIRV_TOOLS", "ON"},
|
||||
},
|
||||
Script: "ctest",
|
||||
},
|
||||
Python,
|
||||
Bash,
|
||||
Diffutils,
|
||||
|
||||
SPIRVTools,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Glslang] = Metadata{
|
||||
f: Toolchain.newGlslang,
|
||||
|
||||
Name: "glslang",
|
||||
Description: "reference front end for GLSL/ESSL",
|
||||
Website: "https://github.com/KhronosGroup/glslang",
|
||||
|
||||
ID: 205796,
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ func (t Toolchain) newM4() (pkg.Artifact, string) {
|
||||
version = "1.4.21"
|
||||
checksum = "pPa6YOo722Jw80l1OsH1tnUaklnPFjFT-bxGw5iAVrZTm1P8FQaWao_NXop46-pm"
|
||||
)
|
||||
return t.NewPackage("m4", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/m4/m4-"+version+".tar.bz2",
|
||||
checksum,
|
||||
return t.NewPackage("m4", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/m4/m4-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -43,9 +43,9 @@ func (t Toolchain) newBison() (pkg.Artifact, string) {
|
||||
version = "3.8.2"
|
||||
checksum = "BhRM6K7URj1LNOkIDCFDctSErLS-Xo5d9ba9seg10o6ACrgC1uNhED7CQPgIY29Y"
|
||||
)
|
||||
return t.NewPackage("bison", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/bison/bison-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("bison", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/bison/bison-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
M4,
|
||||
@@ -70,9 +70,9 @@ func (t Toolchain) newSed() (pkg.Artifact, string) {
|
||||
version = "4.9"
|
||||
checksum = "pe7HWH4PHNYrazOTlUoE1fXmhn2GOPFN_xE62i0llOr3kYGrH1g2_orDz0UtZ9Nt"
|
||||
)
|
||||
return t.NewPackage("sed", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/sed/sed-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("sed", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/sed/sed-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
Diffutils,
|
||||
@@ -95,15 +95,15 @@ func (t Toolchain) newAutoconf() (pkg.Artifact, string) {
|
||||
version = "2.73"
|
||||
checksum = "yGabDTeOfaCUB0JX-h3REYLYzMzvpDwFmFFzHNR7QilChCUNE4hR6q7nma4viDYg"
|
||||
)
|
||||
return t.NewPackage("autoconf", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("autoconf", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Flag: TExclusive,
|
||||
}, &MakeHelper{
|
||||
Check: []string{
|
||||
"TESTSUITEFLAGS=" + jobsFlagE,
|
||||
`TESTSUITEFLAGS="-j$(nproc)"`,
|
||||
"check",
|
||||
},
|
||||
},
|
||||
@@ -135,9 +135,9 @@ func (t Toolchain) newAutomake() (pkg.Artifact, string) {
|
||||
version = "1.18.1"
|
||||
checksum = "FjvLG_GdQP7cThTZJLDMxYpRcKdpAVG-YDs1Fj1yaHlSdh_Kx6nRGN14E0r_BjcG"
|
||||
)
|
||||
return t.NewPackage("automake", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/automake/automake-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("automake", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/automake/automake-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -179,13 +179,13 @@ func (t Toolchain) newLibtool() (pkg.Artifact, string) {
|
||||
version = "2.5.4"
|
||||
checksum = "pa6LSrQggh8mSJHQfwGjysAApmZlGJt8wif2cCLzqAAa2jpsTY0jZ-6stS3BWZ2Q"
|
||||
)
|
||||
return t.NewPackage("libtool", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/libtool/libtool-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libtool", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/libtool/libtool-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
Check: []string{
|
||||
"TESTSUITEFLAGS=" + jobsFlagE,
|
||||
`TESTSUITEFLAGS="-j$(nproc)"`,
|
||||
"check",
|
||||
},
|
||||
},
|
||||
@@ -210,9 +210,9 @@ func (t Toolchain) newGzip() (pkg.Artifact, string) {
|
||||
version = "1.14"
|
||||
checksum = "NWhjUavnNfTDFkZJyAUonL9aCOak8GVajWX2OMlzpFnuI0ErpBFyj88mz2xSjz0q"
|
||||
)
|
||||
return t.NewPackage("gzip", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/gzip/gzip-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("gzip", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/gzip/gzip-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
// dependency loop
|
||||
@@ -236,9 +236,9 @@ func (t Toolchain) newGettext() (pkg.Artifact, string) {
|
||||
version = "1.0"
|
||||
checksum = "3MasKeEdPeFEgWgzsBKk7JqWqql1wEMbgPmzAfs-mluyokoW0N8oQVxPQoOnSdgC"
|
||||
)
|
||||
return t.NewPackage("gettext", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/gettext/gettext-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("gettext", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/gettext/gettext-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -282,9 +282,9 @@ func (t Toolchain) newDiffutils() (pkg.Artifact, string) {
|
||||
version = "3.12"
|
||||
checksum = "9J5VAq5oA7eqwzS1Yvw-l3G5o-TccUrNQR3PvyB_lgdryOFAfxtvQfKfhdpquE44"
|
||||
)
|
||||
return t.NewPackage("diffutils", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/diffutils/diffutils-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("diffutils", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/diffutils/diffutils-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -315,9 +315,9 @@ func (t Toolchain) newPatch() (pkg.Artifact, string) {
|
||||
version = "2.8"
|
||||
checksum = "MA0BQc662i8QYBD-DdGgyyfTwaeALZ1K0yusV9rAmNiIsQdX-69YC4t9JEGXZkeR"
|
||||
)
|
||||
return t.NewPackage("patch", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/patch/patch-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("patch", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/patch/patch-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -347,9 +347,9 @@ func (t Toolchain) newBash() (pkg.Artifact, string) {
|
||||
version = "5.3"
|
||||
checksum = "4LQ_GRoB_ko-Ih8QPf_xRKA02xAm_TOxQgcJLmFDT6udUPxTAWrsj-ZNeuTusyDq"
|
||||
)
|
||||
return t.NewPackage("bash", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/bash/bash-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("bash", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/bash/bash-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Flag: TEarly,
|
||||
@@ -377,9 +377,9 @@ func (t Toolchain) newCoreutils() (pkg.Artifact, string) {
|
||||
version = "9.10"
|
||||
checksum = "o-B9wssRnZySzJUI1ZJAgw-bZtj1RC67R9po2AcM2OjjS8FQIl16IRHpC6IwO30i"
|
||||
)
|
||||
return t.NewPackage("coreutils", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/coreutils/coreutils-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("coreutils", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/coreutils/coreutils-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -516,9 +516,9 @@ func (t Toolchain) newTexinfo() (pkg.Artifact, string) {
|
||||
version = "7.3"
|
||||
checksum = "RRmC8Xwdof7JuZJeWGAQ_GeASIHAuJFQMbNONXBz5InooKIQGmqmWRjGNGEr5n4-"
|
||||
)
|
||||
return t.NewPackage("texinfo", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/texinfo/texinfo-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("texinfo", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/texinfo/texinfo-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
// nonstandard glibc extension
|
||||
@@ -549,9 +549,9 @@ func (t Toolchain) newGperf() (pkg.Artifact, string) {
|
||||
version = "3.3"
|
||||
checksum = "RtIy9pPb_Bb8-31J2Nw-rRGso2JlS-lDlVhuNYhqR7Nt4xM_nObznxAlBMnarJv7"
|
||||
)
|
||||
return t.NewPackage("gperf", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gperf/gperf-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("gperf", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gperf/gperf-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
Diffutils,
|
||||
@@ -574,9 +574,9 @@ func (t Toolchain) newGawk() (pkg.Artifact, string) {
|
||||
version = "5.4.0"
|
||||
checksum = "m0RkIolC-PI7EY5q8pcx5Y-0twlIW0Yp3wXXmV-QaHorSdf8BhZ7kW9F8iWomz0C"
|
||||
)
|
||||
return t.NewPackage("gawk", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/gawk/gawk-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("gawk", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/gawk/gawk-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Flag: TEarly,
|
||||
@@ -602,9 +602,9 @@ func (t Toolchain) newGrep() (pkg.Artifact, string) {
|
||||
version = "3.12"
|
||||
checksum = "qMB4RjaPNRRYsxix6YOrjE8gyAT1zVSTy4nW4wKW9fqa0CHYAuWgPwDTirENzm_1"
|
||||
)
|
||||
return t.NewPackage("grep", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/grep/grep-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("grep", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/grep/grep-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -639,6 +639,7 @@ func (t Toolchain) newFindutils() (pkg.Artifact, string) {
|
||||
nil, "https://ftpmirror.gnu.org/gnu/findutils/findutils-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
ScriptEarly: `
|
||||
echo '#!/bin/sh' > gnulib-tests/test-c32ispunct.sh
|
||||
echo 'int main(){return 0;}' > tests/xargs/test-sigusr.c
|
||||
@@ -666,9 +667,9 @@ func (t Toolchain) newBC() (pkg.Artifact, string) {
|
||||
version = "1.08.2"
|
||||
checksum = "8h6f3hjV80XiFs6v9HOPF2KEyg1kuOgn5eeFdVspV05ODBVQss-ey5glc8AmneLy"
|
||||
)
|
||||
return t.NewPackage("bc", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/bc/bc-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("bc", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/bc/bc-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// source expected to be writable
|
||||
@@ -695,9 +696,9 @@ func (t Toolchain) newLibiconv() (pkg.Artifact, string) {
|
||||
version = "1.19"
|
||||
checksum = "UibB6E23y4MksNqYmCCrA3zTFO6vJugD1DEDqqWYFZNuBsUWMVMcncb_5pPAr88x"
|
||||
)
|
||||
return t.NewPackage("libiconv", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/libiconv/libiconv-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libiconv", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
@@ -718,9 +719,9 @@ func (t Toolchain) newTar() (pkg.Artifact, string) {
|
||||
version = "1.35"
|
||||
checksum = "zSaoSlVUDW0dSfm4sbL4FrXLFR8U40Fh3zY5DWhR5NCIJ6GjU6Kc4VZo2-ZqpBRA"
|
||||
)
|
||||
return t.NewPackage("tar", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/tar/tar-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("tar", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/tar/tar-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
Configure: []KV{
|
||||
@@ -732,7 +733,7 @@ func (t Toolchain) newTar() (pkg.Artifact, string) {
|
||||
// very expensive
|
||||
"TARTEST_SKIP_LARGE_FILES=1",
|
||||
|
||||
"TESTSUITEFLAGS=" + jobsFlagE,
|
||||
`TESTSUITEFLAGS="-j$(nproc)"`,
|
||||
"check",
|
||||
},
|
||||
},
|
||||
@@ -760,9 +761,9 @@ func (t Toolchain) newParallel() (pkg.Artifact, string) {
|
||||
version = "20260322"
|
||||
checksum = "gHoPmFkOO62ev4xW59HqyMlodhjp8LvTsBOwsVKHUUdfrt7KwB8koXmSVqQ4VOrB"
|
||||
)
|
||||
return t.NewPackage("parallel", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/parallel/parallel-"+version+".tar.bz2",
|
||||
checksum,
|
||||
return t.NewPackage("parallel", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/parallel/parallel-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
Perl,
|
||||
@@ -789,9 +790,9 @@ func (t Toolchain) newLibunistring() (pkg.Artifact, string) {
|
||||
version = "1.4.2"
|
||||
checksum = "iW9BbfLoVlXjWoLTZ4AekQSu4cFBnLcZ4W8OHWbv0AhJNgD3j65_zqaLMzFKylg2"
|
||||
)
|
||||
return t.NewPackage("libunistring", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/libunistring/libunistring-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libunistring", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftp.gnu.org/gnu/libunistring/libunistring-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
@@ -822,9 +823,9 @@ func (t Toolchain) newLibtasn1() (pkg.Artifact, string) {
|
||||
version = "4.21.0"
|
||||
checksum = "9DYI3UYbfYLy8JsKUcY6f0irskbfL0fHZA91Q-JEOA3kiUwpodyjemRsYRjUpjuq"
|
||||
)
|
||||
return t.NewPackage("libtasn1", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/libtasn1/libtasn1-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libtasn1", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/libtasn1/libtasn1-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
@@ -845,9 +846,9 @@ func (t Toolchain) newReadline() (pkg.Artifact, string) {
|
||||
version = "8.3"
|
||||
checksum = "r-lcGRJq_MvvBpOq47Z2Y1OI2iqrmtcqhTLVXR0xWo37ZpC2uT_md7gKq5o_qTMV"
|
||||
)
|
||||
return t.NewPackage("readline", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/readline/readline-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("readline", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftp.gnu.org/gnu/readline/readline-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
Configure: []KV{
|
||||
@@ -888,9 +889,10 @@ func (t Toolchain) newGnuTLS() (pkg.Artifact, string) {
|
||||
}
|
||||
}
|
||||
|
||||
return t.NewPackage("gnutls", version, t.newTagRemote(
|
||||
return t.NewPackage("gnutls", version, t.NewViaGit(
|
||||
"https://gitlab.com/gnutls/gnutls.git",
|
||||
version, checksum,
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"bootstrap-remove-gtk-doc", `diff --git a/bootstrap.conf b/bootstrap.conf
|
||||
@@ -1060,9 +1062,9 @@ func (t Toolchain) newBinutils() (pkg.Artifact, string) {
|
||||
version = "2.46.0"
|
||||
checksum = "4kK1_EXQipxSqqyvwD4LbiMLFKCUApjq6PeG4XJP4dzxYGqDeqXfh8zLuTyOuOVR"
|
||||
)
|
||||
return t.NewPackage("binutils", version, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/binutils/binutils-"+version+".tar.bz2",
|
||||
checksum,
|
||||
return t.NewPackage("binutils", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/binutils/binutils-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
Bash,
|
||||
@@ -1085,16 +1087,12 @@ func (t Toolchain) newGMP() (pkg.Artifact, string) {
|
||||
version = "6.3.0"
|
||||
checksum = "yrgbgEDWKDdMWVHh7gPbVl56-sRtVVhfvv0M_LX7xMUUk_mvZ1QOJEAnt7g4i3k5"
|
||||
)
|
||||
return t.NewPackage("gmp", version, newTar(
|
||||
"https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
return t.NewPackage("gmp", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
"gmp-"+version+".tar.bz2",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
"CC=cc",
|
||||
},
|
||||
}, (*MakeHelper)(nil),
|
||||
), nil, (*MakeHelper)(nil),
|
||||
M4,
|
||||
), version
|
||||
}
|
||||
@@ -1115,10 +1113,10 @@ func (t Toolchain) newMPFR() (pkg.Artifact, string) {
|
||||
version = "4.2.2"
|
||||
checksum = "wN3gx0zfIuCn9r3VAn_9bmfvAYILwrRfgBjYSD1IjLqyLrLojNN5vKyQuTE9kA-B"
|
||||
)
|
||||
return t.NewPackage("mpfr", version, newTar(
|
||||
"https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
return t.NewPackage("mpfr", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
"mpfr-"+version+".tar.bz2",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
GMP,
|
||||
@@ -1142,12 +1140,13 @@ func init() {
|
||||
|
||||
func (t Toolchain) newMPC() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.4.1"
|
||||
checksum = "wdXAhplnS89FjVp20m2nC2CmLFQeyQqLpQAfViTy4vPxFdv2WYOTtfBKeIk5_Rec"
|
||||
version = "1.4.0"
|
||||
checksum = "TbrxLiE3ipQrHz_F3Xzz4zqBAnkMWyjhNwIK6wh9360RZ39xMt8rxfW3LxA9SnvU"
|
||||
)
|
||||
return t.NewPackage("mpc", version, t.newTagRemote(
|
||||
return t.NewPackage("mpc", version, t.NewViaGit(
|
||||
"https://gitlab.inria.fr/mpc/mpc.git",
|
||||
version, checksum,
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
// does not find mpc-impl.h otherwise
|
||||
EnterSource: true,
|
||||
@@ -1183,17 +1182,10 @@ func (t Toolchain) newGCC() (pkg.Artifact, string) {
|
||||
version = "15.2.0"
|
||||
checksum = "TXJ5WrbXlGLzy1swghQTr4qxgDCyIZFgJry51XEPTBZ8QYbVmFeB4lZbSMtPJ-a1"
|
||||
)
|
||||
|
||||
var configureExtra []KV
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "arm64":
|
||||
configureExtra = append(configureExtra, KV{"with-multilib-list", "''"})
|
||||
}
|
||||
|
||||
return t.NewPackage("gcc", version, newTar(
|
||||
"https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"+
|
||||
return t.NewPackage("gcc", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"+
|
||||
"gcc-"+version+"/gcc-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
@@ -1355,8 +1347,9 @@ ln -s system/lib /work/
|
||||
// it also saturates the CPU for a consequential amount of time.
|
||||
Flag: TExclusive,
|
||||
}, &MakeHelper{
|
||||
Configure: append([]KV{
|
||||
Configure: []KV{
|
||||
{"disable-multilib"},
|
||||
{"with-multilib-list", `""`},
|
||||
{"enable-default-pie"},
|
||||
{"disable-nls"},
|
||||
{"with-gnu-as"},
|
||||
@@ -1364,7 +1357,7 @@ ln -s system/lib /work/
|
||||
{"with-system-zlib"},
|
||||
{"enable-languages", "c,c++,go"},
|
||||
{"with-native-system-header-dir", "/system/include"},
|
||||
}, configureExtra...),
|
||||
},
|
||||
Make: []string{
|
||||
"BOOT_CFLAGS='-O2 -g'",
|
||||
"bootstrap",
|
||||
|
||||
@@ -21,9 +21,9 @@ cd /work/system/go/src
|
||||
chmod -R +w ..
|
||||
|
||||
./make.bash
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, newTar(
|
||||
"https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz",
|
||||
checksum,
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, pkg.NewHTTPGetTar(
|
||||
nil, "https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
)))
|
||||
}
|
||||
@@ -55,9 +55,9 @@ ln -s \
|
||||
../go/bin/go \
|
||||
../go/bin/gofmt \
|
||||
/work/system/bin
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, newTar(
|
||||
"https://go.dev/dl/go"+version+".src.tar.gz",
|
||||
checksum,
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, pkg.NewHTTPGetTar(
|
||||
nil, "https://go.dev/dl/go"+version+".src.tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
)))
|
||||
}
|
||||
@@ -141,8 +141,8 @@ rm \
|
||||
)
|
||||
|
||||
const (
|
||||
version = "1.26.2"
|
||||
checksum = "v-6BE89_1g3xYf-9oIYpJKFXlo3xKHYJj2_VGkaUq8ZVkIVQmLwrto-xGG03OISH"
|
||||
version = "1.26.1"
|
||||
checksum = "DdC5Ea-aCYPUHNObQh_09uWU0vn4e-8Ben850Vq-5OoamDRrXhuYI4YQ_BOFgaT0"
|
||||
)
|
||||
return t.newGo(
|
||||
version,
|
||||
|
||||
@@ -10,9 +10,10 @@ func (t Toolchain) newGLib() (pkg.Artifact, string) {
|
||||
version = "2.88.0"
|
||||
checksum = "T79Cg4z6j-sDZ2yIwvbY4ccRv2-fbwbqgcw59F5NQ6qJT6z4v261vbYp3dHO6Ma3"
|
||||
)
|
||||
return t.NewPackage("glib", version, t.newTagRemote(
|
||||
return t.NewPackage("glib", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/glib.git",
|
||||
version, checksum,
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
Paths: []pkg.ExecPath{
|
||||
pkg.Path(fhs.AbsEtc.Append(
|
||||
|
||||
@@ -99,7 +99,7 @@ mkdir -p /work/system/bin/
|
||||
f: func(t Toolchain) (pkg.Artifact, string) {
|
||||
return t.newHakurei("-dist", `
|
||||
export HAKUREI_VERSION
|
||||
DESTDIR=/work /usr/src/hakurei/all.sh
|
||||
DESTDIR=/work /usr/src/hakurei/dist/release.sh
|
||||
`, true), hakureiVersion
|
||||
},
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
const hakureiVersion = "0.4.0"
|
||||
const hakureiVersion = "0.3.7"
|
||||
|
||||
// hakureiSource is the source code of a hakurei release.
|
||||
var hakureiSource = newTar(
|
||||
"https://git.gensokyo.uk/rosa/hakurei/archive/"+
|
||||
var hakureiSource = pkg.NewHTTPGetTar(
|
||||
nil, "https://git.gensokyo.uk/rosa/hakurei/archive/"+
|
||||
"v"+hakureiVersion+".tar.gz",
|
||||
"wfQ9DqCW0Fw9o91wj-I55waoqzB-UqzzuC0_2h-P-1M78SgZ1WHSPCDJMth6EyC2",
|
||||
mustDecode("Xh_sdITOATEAQN5_UuaOyrWsgboxorqRO9bml3dGm8GAxF8NFpB7MqhSZgjJxAl2"),
|
||||
pkg.TarGzip,
|
||||
)
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
const kernelVersion = "6.12.81"
|
||||
const kernelVersion = "6.12.80"
|
||||
|
||||
var kernelSource = newTar(
|
||||
"https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
|
||||
var kernelSource = pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
|
||||
"snapshot/linux-"+kernelVersion+".tar.gz",
|
||||
"fBkNwf82DQXh74in6gaF2Jot7Vg-Vlcp9BUtCEipL9mvcM1EXLVFdV7FcrO20Eve",
|
||||
mustDecode("_iJEAYoQISJxefuWZYfv0RPWUmHHIjHQw33Fapix-irXrEIREP5ruK37UJW4uMZO"),
|
||||
pkg.TarGzip,
|
||||
)
|
||||
|
||||
@@ -1221,7 +1221,7 @@ install -Dm0500 \
|
||||
/sbin/depmod
|
||||
|
||||
make \
|
||||
` + jobsFlagE + ` \
|
||||
"-j$(nproc)" \
|
||||
-f /usr/src/kernel/Makefile \
|
||||
O=/tmp/kbuild \
|
||||
LLVM=1 \
|
||||
@@ -1282,14 +1282,14 @@ func init() {
|
||||
|
||||
func (t Toolchain) newFirmware() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "20260410"
|
||||
checksum = "J8PdQlGqwrivpskPzbL6xacqR6mlKtXpe5RpzFfVzKPAgG81ZRXsc3qrxwdGJbil"
|
||||
version = "20260309"
|
||||
checksum = "M1az8BxSiOEH3LA11Trc5VAlakwAHhP7-_LKWg6k-SVIzU3xclMDO4Tiujw1gQrC"
|
||||
)
|
||||
return t.NewPackage("firmware", version, newFromGitLab(
|
||||
"gitlab.com",
|
||||
"kernel-firmware/linux-firmware",
|
||||
version,
|
||||
checksum,
|
||||
return t.NewPackage("firmware", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gitlab.com/kernel-firmware/linux-firmware/-/"+
|
||||
"archive/"+version+"/linux-firmware-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// dedup creates temporary file
|
||||
Writable: true,
|
||||
@@ -1309,7 +1309,7 @@ func (t Toolchain) newFirmware() (pkg.Artifact, string) {
|
||||
"install-zst",
|
||||
},
|
||||
SkipCheck: true, // requires pre-commit
|
||||
Install: "make " + jobsFlagE + " DESTDIR=/work/system dedup",
|
||||
Install: `make "-j$(nproc)" DESTDIR=/work/system dedup`,
|
||||
},
|
||||
Parallel,
|
||||
Rdfind,
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newKmod() (pkg.Artifact, string) {
|
||||
version = "34.2"
|
||||
checksum = "0K7POeTKxMhExsaTsnKAC6LUNsRSfe6sSZxWONPbOu-GI_pXOw3toU_BIoqfBhJV"
|
||||
)
|
||||
return t.NewPackage("kmod", version, newTar(
|
||||
"https://www.kernel.org/pub/linux/utils/kernel/"+
|
||||
return t.NewPackage("kmod", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.kernel.org/pub/linux/utils/kernel/"+
|
||||
"kmod/kmod-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
|
||||
@@ -7,9 +7,10 @@ func (t Toolchain) newLibmd() (pkg.Artifact, string) {
|
||||
version = "1.1.0"
|
||||
checksum = "9apYqPPZm0j5HQT8sCsVIhnVIqRD7XgN7kPIaTwTqnTuUq5waUAMq4M7ev8CODJ1"
|
||||
)
|
||||
return t.NewPackage("libmd", version, t.newTagRemote(
|
||||
return t.NewPackage("libmd", version, t.NewViaGit(
|
||||
"https://git.hadrons.org/git/libmd.git",
|
||||
version, checksum,
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), nil, &MakeHelper{
|
||||
Generate: "echo '" + version + "' > .dist-version && ./autogen",
|
||||
ScriptMakeEarly: `
|
||||
@@ -37,9 +38,10 @@ func (t Toolchain) newLibbsd() (pkg.Artifact, string) {
|
||||
version = "0.12.2"
|
||||
checksum = "NVS0xFLTwSP8JiElEftsZ-e1_C-IgJhHrHE77RwKt5178M7r087waO-zYx2_dfGX"
|
||||
)
|
||||
return t.NewPackage("libbsd", version, t.newTagRemote(
|
||||
return t.NewPackage("libbsd", version, t.NewViaGit(
|
||||
"https://gitlab.freedesktop.org/libbsd/libbsd.git",
|
||||
version, checksum,
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), nil, &MakeHelper{
|
||||
Generate: "echo '" + version + "' > .dist-version && ./autogen",
|
||||
},
|
||||
|
||||
@@ -4,13 +4,13 @@ import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibcap() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.78"
|
||||
checksum = "wFdUkBhFMD9InPnrBZyegWrlPSAg_9JiTBC-eSFyWWlmbzL2qjh2mKxr9Kx2a8ut"
|
||||
version = "2.77"
|
||||
checksum = "2GOTFU4cl2QoS7Dv5wh0c9-hxsQwIzMB9Y_gfAo5xKHqcM13fiHt1RbPkfemzjmB"
|
||||
)
|
||||
return t.NewPackage("libcap", version, newTar(
|
||||
"https://git.kernel.org/pub/scm/libs/libcap/libcap.git/"+
|
||||
return t.NewPackage("libcap", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/libs/libcap/libcap.git/"+
|
||||
"snapshot/libcap-"+version+".tar.gz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// uses source tree as scratch space
|
||||
|
||||
@@ -7,9 +7,9 @@ func (t Toolchain) newLibev() (pkg.Artifact, string) {
|
||||
version = "4.33"
|
||||
checksum = "774eSXV_4k8PySRprUDChbEwsw-kzjIFnJ3MpNOl5zDpamBRvC3BqPyRxvkwcL6_"
|
||||
)
|
||||
return t.NewPackage("libev", version, newTar(
|
||||
"https://dist.schmorp.de/libev/Attic/libev-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libev", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://dist.schmorp.de/libev/Attic/libev-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ func (t Toolchain) newLibexpat() (pkg.Artifact, string) {
|
||||
version = "2.7.5"
|
||||
checksum = "vTRUjjg-qbHSXUBYKXgzVHkUO7UNyuhrkSYrE7ikApQm0g-OvQ8tspw4w55M-1Tp"
|
||||
)
|
||||
return t.NewPackage("libexpat", version, newFromGitHubRelease(
|
||||
"libexpat/libexpat",
|
||||
"R_"+strings.ReplaceAll(version, ".", "_"),
|
||||
"expat-"+version+".tar.bz2",
|
||||
checksum,
|
||||
return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/libexpat/libexpat/releases/download/"+
|
||||
"R_"+strings.ReplaceAll(version, ".", "_")+"/"+
|
||||
"expat-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
Bash,
|
||||
|
||||
@@ -7,11 +7,10 @@ func (t Toolchain) newLibffi() (pkg.Artifact, string) {
|
||||
version = "3.5.2"
|
||||
checksum = "2_Q-ZNBBbVhltfL5zEr0wljxPegUimTK4VeMSiwJEGksls3n4gj3lV0Ly3vviSFH"
|
||||
)
|
||||
return t.NewPackage("libffi", version, newFromGitHubRelease(
|
||||
"libffi/libffi",
|
||||
"v"+version,
|
||||
"libffi-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libffi", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/libffi/libffi/releases/download/"+
|
||||
"v"+version+"/libffi-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
KernelHeaders,
|
||||
|
||||
@@ -7,10 +7,10 @@ func (t Toolchain) newLibgd() (pkg.Artifact, string) {
|
||||
version = "2.3.3"
|
||||
checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36"
|
||||
)
|
||||
return t.NewPackage("libgd", version, newFromGitHubRelease(
|
||||
"libgd/libgd",
|
||||
"gd-"+version,
|
||||
"libgd-"+version+".tar.gz", checksum,
|
||||
return t.NewPackage("libgd", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/libgd/libgd/releases/download/"+
|
||||
"gd-"+version+"/libgd-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
|
||||
@@ -7,11 +7,10 @@ func (t Toolchain) newLibpsl() (pkg.Artifact, string) {
|
||||
version = "0.21.5"
|
||||
checksum = "XjfxSzh7peG2Vg4vJlL8z4JZJLcXqbuP6pLWkrGCmRxlnYUFTKNBqWGHCxEOlCad"
|
||||
)
|
||||
return t.NewPackage("libpsl", version, newFromGitHubRelease(
|
||||
"rockdaboot/libpsl",
|
||||
version,
|
||||
"libpsl-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libpsl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/rockdaboot/libpsl/releases/download/"+
|
||||
version+"/libpsl-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
|
||||
@@ -7,11 +7,10 @@ func (t Toolchain) newLibseccomp() (pkg.Artifact, string) {
|
||||
version = "2.6.0"
|
||||
checksum = "mMu-iR71guPjFbb31u-YexBaanKE_nYPjPux-vuBiPfS_0kbwJdfCGlkofaUm-EY"
|
||||
)
|
||||
return t.NewPackage("libseccomp", version, newFromGitHubRelease(
|
||||
"seccomp/libseccomp",
|
||||
"v"+version,
|
||||
"libseccomp-"+version+".tar.gz",
|
||||
checksum,
|
||||
return t.NewPackage("libseccomp", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/seccomp/libseccomp/releases/download/"+
|
||||
"v"+version+"/libseccomp-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
ScriptEarly: `
|
||||
|
||||
@@ -7,10 +7,11 @@ func (t Toolchain) newLibucontext() (pkg.Artifact, string) {
|
||||
version = "1.5"
|
||||
checksum = "Ggk7FMmDNBdCx1Z9PcNWWW6LSpjGYssn2vU0GK5BLXJYw7ZxZbA2m_eSgT9TFnIG"
|
||||
)
|
||||
return t.NewPackage("libucontext", version, newFromGitHub(
|
||||
"kaniini/libucontext",
|
||||
"libucontext-"+version,
|
||||
checksum,
|
||||
return t.NewPackage("libucontext", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/kaniini/libucontext/archive/refs/tags/"+
|
||||
"libucontext-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// uses source tree as scratch space
|
||||
Writable: true,
|
||||
|
||||
@@ -4,12 +4,13 @@ import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibxml2() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.15.3"
|
||||
checksum = "oWkNe53c3d4Lt4OzrXPHBcOLHJ3TWqpa0x7B7bh_DyZ-uIMiplpdZjQRgRWVal2h"
|
||||
version = "2.15.2"
|
||||
checksum = "zwQvCIBnjzUFY-inX5ckfNT3mIezsCRV55C_Iztde5OnRTB3u33lfO5h03g7DK_8"
|
||||
)
|
||||
return t.NewPackage("libxml2", version, t.newTagRemote(
|
||||
return t.NewPackage("libxml2", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/libxml2.git",
|
||||
"v"+version, checksum,
|
||||
"refs/tags/v"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
// can't create shell.out: Read-only file system
|
||||
Writable: true,
|
||||
|
||||
@@ -7,9 +7,10 @@ func (t Toolchain) newLibxslt() (pkg.Artifact, string) {
|
||||
version = "1.1.45"
|
||||
checksum = "MZc_dyUWpHChkWDKa5iycrECxBsRd4ZMbYfL4VojTbung593mlH2tHGmxYB6NFYT"
|
||||
)
|
||||
return t.NewPackage("libxslt", version, t.newTagRemote(
|
||||
return t.NewPackage("libxslt", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/libxslt.git",
|
||||
"v"+version, checksum,
|
||||
"refs/tags/v"+version,
|
||||
mustDecode(checksum),
|
||||
), nil, &MakeHelper{
|
||||
Generate: "NOCONFIGURE=1 ./autogen.sh",
|
||||
|
||||
|
||||
@@ -1,48 +1,239 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
func init() {
|
||||
artifactsM[llvmSource] = Metadata{
|
||||
f: func(t Toolchain) (pkg.Artifact, string) {
|
||||
return t.NewPatchedSource("llvm", llvmVersion, newFromGitHub(
|
||||
"llvm/llvm-project",
|
||||
"llvmorg-"+llvmVersion,
|
||||
llvmChecksum,
|
||||
), true, llvmPatches...), llvmVersion
|
||||
},
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
Name: "llvm-project",
|
||||
Description: "LLVM monorepo with Rosa OS patches",
|
||||
// llvmAttr holds the attributes that will be applied to a new [pkg.Artifact]
|
||||
// containing a LLVM variant.
|
||||
type llvmAttr struct {
|
||||
// Passed through to PackageAttr.Flag.
|
||||
flags int
|
||||
|
||||
ID: 1830,
|
||||
// Concatenated with default environment for PackageAttr.Env.
|
||||
env []string
|
||||
// Concatenated with generated entries for CMakeHelper.Cache.
|
||||
cmake []KV
|
||||
// Override CMakeHelper.Append.
|
||||
append []string
|
||||
// Passed through to PackageAttr.NonStage0.
|
||||
nonStage0 []pkg.Artifact
|
||||
// Passed through to PackageAttr.Paths.
|
||||
paths []pkg.ExecPath
|
||||
// Concatenated with default fixup for CMakeHelper.Script.
|
||||
script string
|
||||
|
||||
// Patch name and body pairs.
|
||||
patches []KV
|
||||
}
|
||||
|
||||
const (
|
||||
llvmProjectClang = 1 << iota
|
||||
llvmProjectLld
|
||||
|
||||
llvmProjectAll = 1<<iota - 1
|
||||
|
||||
llvmRuntimeCompilerRT = 1 << iota
|
||||
llvmRuntimeLibunwind
|
||||
llvmRuntimeLibc
|
||||
llvmRuntimeLibcxx
|
||||
llvmRuntimeLibcxxABI
|
||||
|
||||
llvmAll = 1<<iota - 1
|
||||
llvmRuntimeAll = llvmAll - (2 * llvmProjectAll) - 1
|
||||
)
|
||||
|
||||
// llvmFlagName resolves a llvmAttr.flags project or runtime flag to its name.
|
||||
func llvmFlagName(flag int) string {
|
||||
switch flag {
|
||||
case llvmProjectClang:
|
||||
return "clang"
|
||||
case llvmProjectLld:
|
||||
return "lld"
|
||||
|
||||
case llvmRuntimeCompilerRT:
|
||||
return "compiler-rt"
|
||||
case llvmRuntimeLibunwind:
|
||||
return "libunwind"
|
||||
case llvmRuntimeLibc:
|
||||
return "libc"
|
||||
case llvmRuntimeLibcxx:
|
||||
return "libcxx"
|
||||
case llvmRuntimeLibcxxABI:
|
||||
return "libcxxabi"
|
||||
|
||||
default:
|
||||
panic("invalid flag " + strconv.Itoa(flag))
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newCompilerRT() (pkg.Artifact, string) {
|
||||
muslHeaders, _ := t.newMusl(true)
|
||||
return t.NewPackage("compiler-rt", llvmVersion, t.Load(llvmSource), &PackageAttr{
|
||||
NonStage0: []pkg.Artifact{
|
||||
muslHeaders,
|
||||
},
|
||||
Env: stage0ExclConcat(t, []string{},
|
||||
// newLLVMVariant returns a [pkg.Artifact] containing a LLVM variant.
|
||||
func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
|
||||
|
||||
if attr == nil {
|
||||
panic("LLVM attr must be non-nil")
|
||||
}
|
||||
|
||||
var projects, runtimes []string
|
||||
for i := 1; i < llvmProjectAll; i <<= 1 {
|
||||
if attr.flags&i != 0 {
|
||||
projects = append(projects, llvmFlagName(i))
|
||||
}
|
||||
}
|
||||
for i := (llvmProjectAll + 1) << 1; i < llvmRuntimeAll; i <<= 1 {
|
||||
if attr.flags&i != 0 {
|
||||
runtimes = append(runtimes, llvmFlagName(i))
|
||||
}
|
||||
}
|
||||
|
||||
var script string
|
||||
|
||||
cache := []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
|
||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
}
|
||||
if len(projects) > 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"LLVM_ENABLE_PROJECTS", `"${ROSA_LLVM_PROJECTS}"`},
|
||||
}...)
|
||||
}
|
||||
if len(runtimes) > 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"LLVM_ENABLE_RUNTIMES", `"${ROSA_LLVM_RUNTIMES}"`},
|
||||
}...)
|
||||
}
|
||||
|
||||
cmakeAppend := []string{"llvm"}
|
||||
if attr.append != nil {
|
||||
cmakeAppend = attr.append
|
||||
} else {
|
||||
cache = append(cache, []KV{
|
||||
{"LLVM_ENABLE_LIBCXX", "ON"},
|
||||
{"LLVM_USE_LINKER", "lld"},
|
||||
|
||||
{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
||||
{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
||||
|
||||
{"LLVM_LIT_ARGS", "'--verbose'"},
|
||||
}...)
|
||||
}
|
||||
|
||||
if attr.flags&llvmProjectClang != 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"CLANG_DEFAULT_LINKER", "lld"},
|
||||
{"CLANG_DEFAULT_CXX_STDLIB", "libc++"},
|
||||
{"CLANG_DEFAULT_RTLIB", "compiler-rt"},
|
||||
{"CLANG_DEFAULT_UNWINDLIB", "libunwind"},
|
||||
}...)
|
||||
}
|
||||
if attr.flags&llvmProjectLld != 0 {
|
||||
script += `
|
||||
ln -s ld.lld /work/system/bin/ld
|
||||
`
|
||||
}
|
||||
if attr.flags&llvmRuntimeCompilerRT != 0 {
|
||||
if attr.append == nil {
|
||||
cache = append(cache, []KV{
|
||||
{"COMPILER_RT_USE_LLVM_UNWINDER", "ON"},
|
||||
}...)
|
||||
}
|
||||
}
|
||||
if attr.flags&llvmRuntimeLibunwind != 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"LIBUNWIND_USE_COMPILER_RT", "ON"},
|
||||
}...)
|
||||
}
|
||||
if attr.flags&llvmRuntimeLibcxx != 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"LIBCXX_HAS_MUSL_LIBC", "ON"},
|
||||
{"LIBCXX_USE_COMPILER_RT", "ON"},
|
||||
}...)
|
||||
}
|
||||
if attr.flags&llvmRuntimeLibcxxABI != 0 {
|
||||
cache = append(cache, []KV{
|
||||
{"LIBCXXABI_USE_COMPILER_RT", "ON"},
|
||||
{"LIBCXXABI_USE_LLVM_UNWINDER", "ON"},
|
||||
}...)
|
||||
}
|
||||
|
||||
return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+
|
||||
"llvmorg-"+llvmVersion+".tar.gz",
|
||||
mustDecode(llvmChecksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: attr.patches,
|
||||
NonStage0: attr.nonStage0,
|
||||
|
||||
Env: slices.Concat([]string{
|
||||
"ROSA_LLVM_PROJECTS=" + strings.Join(projects, ";"),
|
||||
"ROSA_LLVM_RUNTIMES=" + strings.Join(runtimes, ";"),
|
||||
}, attr.env),
|
||||
|
||||
Paths: attr.paths,
|
||||
Flag: TExclusive,
|
||||
}, &CMakeHelper{
|
||||
Variant: variant,
|
||||
|
||||
Cache: slices.Concat(cache, attr.cmake),
|
||||
Append: cmakeAppend,
|
||||
Script: script + attr.script,
|
||||
},
|
||||
Python,
|
||||
Perl,
|
||||
Diffutils,
|
||||
Bash,
|
||||
Gawk,
|
||||
Coreutils,
|
||||
Findutils,
|
||||
|
||||
KernelHeaders,
|
||||
)
|
||||
}
|
||||
|
||||
// newLLVM returns LLVM toolchain across multiple [pkg.Artifact].
|
||||
func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
|
||||
var target string
|
||||
switch runtime.GOARCH {
|
||||
case "386", "amd64":
|
||||
target = "X86"
|
||||
case "arm64":
|
||||
target = "AArch64"
|
||||
case "riscv64":
|
||||
target = "RISCV"
|
||||
|
||||
default:
|
||||
panic("unsupported target " + runtime.GOARCH)
|
||||
}
|
||||
|
||||
minimalDeps := []KV{
|
||||
{"LLVM_ENABLE_ZLIB", "OFF"},
|
||||
{"LLVM_ENABLE_ZSTD", "OFF"},
|
||||
{"LLVM_ENABLE_LIBXML2", "OFF"},
|
||||
}
|
||||
|
||||
muslHeaders, _ := t.newMusl(true, []string{
|
||||
"CC=clang",
|
||||
})
|
||||
|
||||
compilerRT = t.newLLVMVariant("compiler-rt", &llvmAttr{
|
||||
env: stage0ExclConcat(t, []string{},
|
||||
"LDFLAGS="+earlyLDFLAGS(false),
|
||||
),
|
||||
Flag: TExclusive,
|
||||
}, &CMakeHelper{
|
||||
Append: []string{"compiler-rt"},
|
||||
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
|
||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
|
||||
cmake: []KV{
|
||||
// libc++ not yet available
|
||||
{"CMAKE_CXX_COMPILER_TARGET", ""},
|
||||
|
||||
{"COMPILER_RT_BUILD_BUILTINS", "ON"},
|
||||
{"COMPILER_RT_DEFAULT_TARGET_ONLY", "OFF"},
|
||||
{"COMPILER_RT_DEFAULT_TARGET_ONLY", "ON"},
|
||||
{"COMPILER_RT_SANITIZERS_TO_BUILD", "asan"},
|
||||
{"LLVM_ENABLE_PER_TARGET_RUNTIME_DIR", "ON"},
|
||||
|
||||
@@ -53,7 +244,11 @@ func (t Toolchain) newCompilerRT() (pkg.Artifact, string) {
|
||||
{"COMPILER_RT_BUILD_PROFILE", "OFF"},
|
||||
{"COMPILER_RT_BUILD_XRAY", "OFF"},
|
||||
},
|
||||
Script: `
|
||||
append: []string{"compiler-rt"},
|
||||
nonStage0: []pkg.Artifact{
|
||||
muslHeaders,
|
||||
},
|
||||
script: `
|
||||
mkdir -p "/work/system/lib/clang/` + llvmVersionMajor + `/lib/"
|
||||
ln -s \
|
||||
"../../../${ROSA_TRIPLE}" \
|
||||
@@ -66,179 +261,286 @@ ln -s \
|
||||
"clang_rt.crtend-` + linuxArch() + `.o" \
|
||||
"/work/system/lib/${ROSA_TRIPLE}/crtendS.o"
|
||||
`,
|
||||
})
|
||||
|
||||
musl, _ = t.newMusl(false, stage0ExclConcat(t, []string{
|
||||
"CC=clang",
|
||||
"LIBCC=/system/lib/clang/" + llvmVersionMajor + "/lib/" +
|
||||
triplet() + "/libclang_rt.builtins.a",
|
||||
"AR=ar",
|
||||
"RANLIB=ranlib",
|
||||
},
|
||||
Python,
|
||||
"LDFLAGS="+earlyLDFLAGS(false),
|
||||
), compilerRT)
|
||||
|
||||
KernelHeaders,
|
||||
), llvmVersion
|
||||
}
|
||||
func init() {
|
||||
artifactsM[CompilerRT] = Metadata{
|
||||
f: Toolchain.newCompilerRT,
|
||||
|
||||
Name: "compiler-rt",
|
||||
Description: "LLVM runtime: compiler-rt",
|
||||
Website: "https://llvm.org/",
|
||||
|
||||
Dependencies: P{
|
||||
Musl,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLLVMRuntimes() (pkg.Artifact, string) {
|
||||
return t.NewPackage("llvm-runtimes", llvmVersion, t.Load(llvmSource), &PackageAttr{
|
||||
NonStage0: t.AppendPresets(nil, CompilerRT),
|
||||
Env: stage0ExclConcat(t, []string{},
|
||||
runtimes = t.newLLVMVariant("runtimes", &llvmAttr{
|
||||
env: stage0ExclConcat(t, []string{},
|
||||
"LDFLAGS="+earlyLDFLAGS(false),
|
||||
),
|
||||
Flag: TExclusive,
|
||||
}, &CMakeHelper{
|
||||
Append: []string{"runtimes"},
|
||||
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
|
||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_ENABLE_RUNTIMES", "'libunwind;libcxx;libcxxabi'"},
|
||||
|
||||
{"LIBUNWIND_USE_COMPILER_RT", "ON"},
|
||||
{"LIBCXX_HAS_MUSL_LIBC", "ON"},
|
||||
{"LIBCXX_USE_COMPILER_RT", "ON"},
|
||||
{"LIBCXXABI_USE_COMPILER_RT", "ON"},
|
||||
{"LIBCXXABI_USE_LLVM_UNWINDER", "ON"},
|
||||
|
||||
flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI,
|
||||
cmake: slices.Concat([]KV{
|
||||
// libc++ not yet available
|
||||
{"CMAKE_CXX_COMPILER_WORKS", "ON"},
|
||||
|
||||
{"LIBCXX_HAS_ATOMIC_LIB", "OFF"},
|
||||
{"LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL", "OFF"},
|
||||
|
||||
{"LLVM_ENABLE_ZLIB", "OFF"},
|
||||
{"LLVM_ENABLE_ZSTD", "OFF"},
|
||||
{"LLVM_ENABLE_LIBXML2", "OFF"},
|
||||
}, minimalDeps),
|
||||
append: []string{"runtimes"},
|
||||
nonStage0: []pkg.Artifact{
|
||||
compilerRT,
|
||||
musl,
|
||||
},
|
||||
},
|
||||
Python,
|
||||
})
|
||||
|
||||
KernelHeaders,
|
||||
), llvmVersion
|
||||
}
|
||||
func init() {
|
||||
artifactsM[LLVMRuntimes] = Metadata{
|
||||
f: Toolchain.newLLVMRuntimes,
|
||||
|
||||
Name: "llvm-runtimes",
|
||||
Description: "LLVM runtimes: libunwind, libcxx, libcxxabi",
|
||||
Website: "https://llvm.org/",
|
||||
|
||||
Dependencies: P{
|
||||
CompilerRT,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newClang() (pkg.Artifact, string) {
|
||||
target := "'AArch64;RISCV;X86'"
|
||||
if t.isStage0() {
|
||||
target = "Native"
|
||||
}
|
||||
|
||||
return t.NewPackage("clang", llvmVersion, t.Load(llvmSource), &PackageAttr{
|
||||
NonStage0: t.AppendPresets(nil, LLVMRuntimes),
|
||||
Env: stage0ExclConcat(t, []string{},
|
||||
clang = t.newLLVMVariant("clang", &llvmAttr{
|
||||
flags: llvmProjectClang | llvmProjectLld,
|
||||
env: stage0ExclConcat(t, []string{},
|
||||
"CFLAGS="+earlyCFLAGS,
|
||||
"CXXFLAGS="+earlyCXXFLAGS(),
|
||||
"LDFLAGS="+earlyLDFLAGS(false),
|
||||
),
|
||||
Flag: TExclusive,
|
||||
}, &CMakeHelper{
|
||||
Append: []string{"llvm"},
|
||||
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
|
||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_ENABLE_PROJECTS", "'clang;lld'"},
|
||||
|
||||
{"LLVM_ENABLE_LIBCXX", "ON"},
|
||||
{"LLVM_USE_LINKER", "lld"},
|
||||
|
||||
{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
||||
{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
||||
|
||||
{"LLVM_LIT_ARGS", "'--verbose'"},
|
||||
|
||||
{"CLANG_DEFAULT_LINKER", "lld"},
|
||||
{"CLANG_DEFAULT_CXX_STDLIB", "libc++"},
|
||||
{"CLANG_DEFAULT_RTLIB", "compiler-rt"},
|
||||
{"CLANG_DEFAULT_UNWINDLIB", "libunwind"},
|
||||
|
||||
cmake: slices.Concat([]KV{
|
||||
{"LLVM_TARGETS_TO_BUILD", target},
|
||||
{"CMAKE_CROSSCOMPILING", "OFF"},
|
||||
{"CXX_SUPPORTS_CUSTOM_LINKER", "ON"},
|
||||
|
||||
{"LLVM_ENABLE_ZLIB", "OFF"},
|
||||
{"LLVM_ENABLE_ZSTD", "OFF"},
|
||||
{"LLVM_ENABLE_LIBXML2", "OFF"},
|
||||
}, minimalDeps),
|
||||
nonStage0: []pkg.Artifact{
|
||||
musl,
|
||||
compilerRT,
|
||||
runtimes,
|
||||
},
|
||||
Script: `
|
||||
ln -s ld.lld /work/system/bin/ld
|
||||
|
||||
script: `
|
||||
ln -s clang /work/system/bin/cc
|
||||
ln -s clang++ /work/system/bin/c++
|
||||
|
||||
ninja ` + jobsFlagE + ` check-all
|
||||
ninja check-all
|
||||
`,
|
||||
},
|
||||
Python,
|
||||
Perl,
|
||||
Diffutils,
|
||||
Bash,
|
||||
Gawk,
|
||||
Coreutils,
|
||||
Findutils,
|
||||
|
||||
KernelHeaders,
|
||||
), llvmVersion
|
||||
patches: slices.Concat([]KV{
|
||||
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
|
||||
index 9c83abeeb3b1..5acfe5836a23 100644
|
||||
--- a/llvm/include/llvm/TargetParser/Triple.h
|
||||
+++ b/llvm/include/llvm/TargetParser/Triple.h
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
|
||||
Apple,
|
||||
PC,
|
||||
+ Rosa,
|
||||
SCEI,
|
||||
Freescale,
|
||||
IBM,
|
||||
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
|
||||
index a4f9dd42c0fe..cb5a12387034 100644
|
||||
--- a/llvm/lib/TargetParser/Triple.cpp
|
||||
+++ b/llvm/lib/TargetParser/Triple.cpp
|
||||
@@ -279,6 +279,7 @@ StringRef Triple::getVendorTypeName(VendorType Kind) {
|
||||
case NVIDIA: return "nvidia";
|
||||
case OpenEmbedded: return "oe";
|
||||
case PC: return "pc";
|
||||
+ case Rosa: return "rosa";
|
||||
case SCEI: return "scei";
|
||||
case SUSE: return "suse";
|
||||
case Meta:
|
||||
@@ -689,6 +690,7 @@ static Triple::VendorType parseVendor(StringRef VendorName) {
|
||||
return StringSwitch<Triple::VendorType>(VendorName)
|
||||
.Case("apple", Triple::Apple)
|
||||
.Case("pc", Triple::PC)
|
||||
+ .Case("rosa", Triple::Rosa)
|
||||
.Case("scei", Triple::SCEI)
|
||||
.Case("sie", Triple::SCEI)
|
||||
.Case("fsl", Triple::Freescale)
|
||||
`},
|
||||
|
||||
{"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c
|
||||
index 50fdce630255..4b4465a75617 100644
|
||||
--- a/clang/test/Modules/timestamps.c
|
||||
+++ b/clang/test/Modules/timestamps.c
|
||||
@@ -1,3 +1,5 @@
|
||||
+// XFAIL: target={{.*-rosa-linux-musl}}
|
||||
+
|
||||
/// Verify timestamps that gets embedded in the module
|
||||
#include <c-header.h>
|
||||
|
||||
`},
|
||||
|
||||
{"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
index 8ac8d4eb9181..e46b04a898ca 100644
|
||||
--- a/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
@@ -671,6 +671,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
|
||||
addExternCSystemInclude(
|
||||
DriverArgs, CC1Args,
|
||||
concat(SysRoot, "/usr/include", MultiarchIncludeDir));
|
||||
+ if (!MultiarchIncludeDir.empty() &&
|
||||
+ D.getVFS().exists(concat(SysRoot, "/system/include", MultiarchIncludeDir)))
|
||||
+ addExternCSystemInclude(
|
||||
+ DriverArgs, CC1Args,
|
||||
+ concat(SysRoot, "/system/include", MultiarchIncludeDir));
|
||||
+
|
||||
|
||||
if (getTriple().getOS() == llvm::Triple::RTEMS)
|
||||
return;
|
||||
@@ -681,6 +687,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
|
||||
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include"));
|
||||
|
||||
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include"));
|
||||
+ addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include"));
|
||||
|
||||
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
|
||||
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
|
||||
`},
|
||||
|
||||
{"path-system-libraries", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
index 8ac8d4eb9181..f4d1347ab64d 100644
|
||||
--- a/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
@@ -282,6 +282,7 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
const bool IsHexagon = Arch == llvm::Triple::hexagon;
|
||||
const bool IsRISCV = Triple.isRISCV();
|
||||
const bool IsCSKY = Triple.isCSKY();
|
||||
+ const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa;
|
||||
|
||||
if (IsCSKY && !SelectedMultilibs.empty())
|
||||
SysRoot = SysRoot + SelectedMultilibs.back().osSuffix();
|
||||
@@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
const std::string OSLibDir = std::string(getOSLibDir(Triple, Args));
|
||||
const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot);
|
||||
|
||||
+ if (IsRosa) {
|
||||
+ ExtraOpts.push_back("-rpath");
|
||||
+ ExtraOpts.push_back("/system/lib");
|
||||
+ ExtraOpts.push_back("-rpath");
|
||||
+ ExtraOpts.push_back(concat("/system/lib", MultiarchTriple));
|
||||
+ }
|
||||
+
|
||||
// mips32: Debian multilib, we use /libo32, while in other case, /lib is
|
||||
// used. We need add both libo32 and /lib.
|
||||
if (Arch == llvm::Triple::mips || Arch == llvm::Triple::mipsel) {
|
||||
Generic_GCC::AddMultilibPaths(D, SysRoot, "libo32", MultiarchTriple, Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/libo32"), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/libo32"), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/libo32"), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/libo32"), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/libo32"), Paths);
|
||||
+ }
|
||||
}
|
||||
Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths);
|
||||
|
||||
@@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
Paths);
|
||||
}
|
||||
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/lib", MultiarchTriple), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir), Paths);
|
||||
+ }
|
||||
if (IsRISCV) {
|
||||
StringRef ABIName = tools::riscv::getRISCVABI(Args, Triple);
|
||||
addPathIfExists(D, concat(SysRoot, "/", OSLibDir, ABIName), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir, ABIName), Paths);
|
||||
+ if (!IsRosa)
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir, ABIName), Paths);
|
||||
+ else
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths);
|
||||
}
|
||||
|
||||
Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths);
|
||||
|
||||
- addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths);
|
||||
+ }
|
||||
}
|
||||
|
||||
ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const {
|
||||
@@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const {
|
||||
return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker";
|
||||
}
|
||||
if (Triple.isMusl()) {
|
||||
+ if (Triple.getVendor() == llvm::Triple::Rosa)
|
||||
+ return "/system/bin/linker";
|
||||
+
|
||||
std::string ArchName;
|
||||
bool IsArm = false;
|
||||
|
||||
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
|
||||
index 64324a3f8b01..15ce70b68217 100644
|
||||
--- a/clang/tools/clang-installapi/Options.cpp
|
||||
+++ b/clang/tools/clang-installapi/Options.cpp
|
||||
@@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) {
|
||||
FEOpts.FwkPaths = std::move(FrameworkPaths);
|
||||
|
||||
// Add default framework/library paths.
|
||||
- PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"};
|
||||
+ PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"};
|
||||
PathSeq DefaultFrameworkPaths = {"/Library/Frameworks",
|
||||
"/System/Library/Frameworks"};
|
||||
|
||||
`},
|
||||
}, clangPatches),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Clang] = Metadata{
|
||||
f: Toolchain.newClang,
|
||||
artifactsM[LLVMCompilerRT] = Metadata{
|
||||
f: func(t Toolchain) (pkg.Artifact, string) {
|
||||
_, compilerRT, _, _ := t.newLLVM()
|
||||
return compilerRT, llvmVersion
|
||||
},
|
||||
|
||||
Name: "llvm-compiler-rt",
|
||||
Description: "LLVM runtime: compiler-rt",
|
||||
Website: "https://llvm.org/",
|
||||
}
|
||||
|
||||
artifactsM[LLVMRuntimes] = Metadata{
|
||||
f: func(t Toolchain) (pkg.Artifact, string) {
|
||||
_, _, runtimes, _ := t.newLLVM()
|
||||
return runtimes, llvmVersion
|
||||
},
|
||||
|
||||
Name: "llvm-runtimes",
|
||||
Description: "LLVM runtimes: libunwind, libcxx, libcxxabi",
|
||||
Website: "https://llvm.org/",
|
||||
}
|
||||
|
||||
artifactsM[LLVMClang] = Metadata{
|
||||
f: func(t Toolchain) (pkg.Artifact, string) {
|
||||
_, _, _, clang := t.newLLVM()
|
||||
return clang, llvmVersion
|
||||
},
|
||||
|
||||
Name: "clang",
|
||||
Description: `an "LLVM native" C/C++/Objective-C compiler`,
|
||||
Website: "https://llvm.org/",
|
||||
|
||||
Dependencies: P{
|
||||
LLVMRuntimes,
|
||||
},
|
||||
ID: 1830,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibclc() (pkg.Artifact, string) {
|
||||
return t.NewPackage("libclc", llvmVersion, t.Load(llvmSource), nil, &CMakeHelper{
|
||||
Append: []string{"libclc"},
|
||||
var (
|
||||
// llvm stores the result of Toolchain.newLLVM.
|
||||
llvm [_toolchainEnd][4]pkg.Artifact
|
||||
// llvmOnce is for lazy initialisation of llvm.
|
||||
llvmOnce [_toolchainEnd]sync.Once
|
||||
)
|
||||
|
||||
Cache: []KV{
|
||||
{"CMAKE_BUILD_TYPE", "Release"},
|
||||
|
||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||
|
||||
{"LIBCLC_TARGETS_TO_BUILD", "all"},
|
||||
},
|
||||
Script: "ninja " + jobsFlagE + " test",
|
||||
}), llvmVersion
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libclc] = Metadata{
|
||||
f: Toolchain.newLibclc,
|
||||
|
||||
Name: "libclc",
|
||||
Description: "an open source, BSD/MIT dual licensed implementation of the library requirements of the OpenCL C programming language",
|
||||
Website: "https://libclc.llvm.org/",
|
||||
}
|
||||
// NewLLVM returns LLVM toolchain across multiple [pkg.Artifact].
|
||||
func (t Toolchain) NewLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
|
||||
llvmOnce[t].Do(func() {
|
||||
llvm[t][0], llvm[t][1], llvm[t][2], llvm[t][3] = t.newLLVM()
|
||||
})
|
||||
return llvm[t][0], llvm[t][1], llvm[t][2], llvm[t][3]
|
||||
}
|
||||
|
||||
4
internal/rosa/llvm_amd64.go
Normal file
4
internal/rosa/llvm_amd64.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package rosa
|
||||
|
||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||
var clangPatches []KV
|
||||
12
internal/rosa/llvm_arm64.go
Normal file
12
internal/rosa/llvm_arm64.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package rosa
|
||||
|
||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||
var clangPatches []KV
|
||||
|
||||
// one version behind, latest fails 5 tests with 2 flaky on arm64
|
||||
const (
|
||||
llvmVersionMajor = "21"
|
||||
llvmVersion = llvmVersionMajor + ".1.8"
|
||||
|
||||
llvmChecksum = "8SUpqDkcgwOPsqHVtmf9kXfFeVmjVxl4LMn-qSE1AI_Xoeju-9HaoPNGtidyxyka"
|
||||
)
|
||||
@@ -1,9 +1,11 @@
|
||||
//go:build !arm64
|
||||
|
||||
package rosa
|
||||
|
||||
// latest version of LLVM, conditional to temporarily avoid broken new releases
|
||||
const (
|
||||
llvmVersionMajor = "22"
|
||||
llvmVersion = llvmVersionMajor + ".1.3"
|
||||
llvmVersion = llvmVersionMajor + ".1.2"
|
||||
|
||||
llvmChecksum = "CUwnpzua_y28HZ9oI0NmcKL2wClsSjFpgY9do5-7cCZJHI5KNF64vfwGvY0TYyR3"
|
||||
llvmChecksum = "FwsmurWDVyYYQlOowowFjekwIGSB5__aKTpW_VGP3eWoZGXvBny-bOn1DuQ1U5xE"
|
||||
)
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
package rosa
|
||||
|
||||
// llvmPatches are centralised patches against latest LLVM monorepo.
|
||||
var llvmPatches = []KV{
|
||||
{"increase-stack-size-unconditional", `diff --git a/llvm/lib/Support/Threading.cpp b/llvm/lib/Support/Threading.cpp
|
||||
index 9da357a7ebb9..b2931510c1ae 100644
|
||||
--- a/llvm/lib/Support/Threading.cpp
|
||||
+++ b/llvm/lib/Support/Threading.cpp
|
||||
@@ -80,7 +80,7 @@ unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
|
||||
// keyword.
|
||||
#include "llvm/Support/thread.h"
|
||||
|
||||
-#if defined(__APPLE__)
|
||||
+#if defined(__APPLE__) || 1
|
||||
// Darwin's default stack size for threads except the main one is only 512KB,
|
||||
// which is not enough for some/many normal LLVM compilations. This implements
|
||||
// the same interface as std::thread but requests the same stack size as the
|
||||
`},
|
||||
|
||||
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
|
||||
index 9c83abeeb3b1..5acfe5836a23 100644
|
||||
--- a/llvm/include/llvm/TargetParser/Triple.h
|
||||
+++ b/llvm/include/llvm/TargetParser/Triple.h
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
|
||||
Apple,
|
||||
PC,
|
||||
+ Rosa,
|
||||
SCEI,
|
||||
Freescale,
|
||||
IBM,
|
||||
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
|
||||
index a4f9dd42c0fe..cb5a12387034 100644
|
||||
--- a/llvm/lib/TargetParser/Triple.cpp
|
||||
+++ b/llvm/lib/TargetParser/Triple.cpp
|
||||
@@ -279,6 +279,7 @@ StringRef Triple::getVendorTypeName(VendorType Kind) {
|
||||
case NVIDIA: return "nvidia";
|
||||
case OpenEmbedded: return "oe";
|
||||
case PC: return "pc";
|
||||
+ case Rosa: return "rosa";
|
||||
case SCEI: return "scei";
|
||||
case SUSE: return "suse";
|
||||
case Meta:
|
||||
@@ -689,6 +690,7 @@ static Triple::VendorType parseVendor(StringRef VendorName) {
|
||||
return StringSwitch<Triple::VendorType>(VendorName)
|
||||
.Case("apple", Triple::Apple)
|
||||
.Case("pc", Triple::PC)
|
||||
+ .Case("rosa", Triple::Rosa)
|
||||
.Case("scei", Triple::SCEI)
|
||||
.Case("sie", Triple::SCEI)
|
||||
.Case("fsl", Triple::Freescale)
|
||||
`},
|
||||
|
||||
{"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c
|
||||
index 50fdce630255..4b4465a75617 100644
|
||||
--- a/clang/test/Modules/timestamps.c
|
||||
+++ b/clang/test/Modules/timestamps.c
|
||||
@@ -1,3 +1,5 @@
|
||||
+// XFAIL: target={{.*-rosa-linux-musl}}
|
||||
+
|
||||
/// Verify timestamps that gets embedded in the module
|
||||
#include <c-header.h>
|
||||
|
||||
`},
|
||||
|
||||
{"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
index 8ac8d4eb9181..e46b04a898ca 100644
|
||||
--- a/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
@@ -671,6 +671,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
|
||||
addExternCSystemInclude(
|
||||
DriverArgs, CC1Args,
|
||||
concat(SysRoot, "/usr/include", MultiarchIncludeDir));
|
||||
+ if (!MultiarchIncludeDir.empty() &&
|
||||
+ D.getVFS().exists(concat(SysRoot, "/system/include", MultiarchIncludeDir)))
|
||||
+ addExternCSystemInclude(
|
||||
+ DriverArgs, CC1Args,
|
||||
+ concat(SysRoot, "/system/include", MultiarchIncludeDir));
|
||||
+
|
||||
|
||||
if (getTriple().getOS() == llvm::Triple::RTEMS)
|
||||
return;
|
||||
@@ -681,6 +687,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
|
||||
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include"));
|
||||
|
||||
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include"));
|
||||
+ addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include"));
|
||||
|
||||
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
|
||||
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
|
||||
`},
|
||||
|
||||
{"path-system-libraries", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
index 8ac8d4eb9181..f4d1347ab64d 100644
|
||||
--- a/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
|
||||
@@ -282,6 +282,7 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
const bool IsHexagon = Arch == llvm::Triple::hexagon;
|
||||
const bool IsRISCV = Triple.isRISCV();
|
||||
const bool IsCSKY = Triple.isCSKY();
|
||||
+ const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa;
|
||||
|
||||
if (IsCSKY && !SelectedMultilibs.empty())
|
||||
SysRoot = SysRoot + SelectedMultilibs.back().osSuffix();
|
||||
@@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
const std::string OSLibDir = std::string(getOSLibDir(Triple, Args));
|
||||
const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot);
|
||||
|
||||
+ if (IsRosa) {
|
||||
+ ExtraOpts.push_back("-rpath");
|
||||
+ ExtraOpts.push_back("/system/lib");
|
||||
+ ExtraOpts.push_back("-rpath");
|
||||
+ ExtraOpts.push_back(concat("/system/lib", MultiarchTriple));
|
||||
+ }
|
||||
+
|
||||
// mips32: Debian multilib, we use /libo32, while in other case, /lib is
|
||||
// used. We need add both libo32 and /lib.
|
||||
if (Arch == llvm::Triple::mips || Arch == llvm::Triple::mipsel) {
|
||||
Generic_GCC::AddMultilibPaths(D, SysRoot, "libo32", MultiarchTriple, Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/libo32"), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/libo32"), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/libo32"), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/libo32"), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/libo32"), Paths);
|
||||
+ }
|
||||
}
|
||||
Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths);
|
||||
|
||||
@@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
|
||||
Paths);
|
||||
}
|
||||
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/lib", MultiarchTriple), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir), Paths);
|
||||
+ }
|
||||
if (IsRISCV) {
|
||||
StringRef ABIName = tools::riscv::getRISCVABI(Args, Triple);
|
||||
addPathIfExists(D, concat(SysRoot, "/", OSLibDir, ABIName), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir, ABIName), Paths);
|
||||
+ if (!IsRosa)
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir, ABIName), Paths);
|
||||
+ else
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths);
|
||||
}
|
||||
|
||||
Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths);
|
||||
|
||||
- addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
|
||||
- addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
|
||||
+ if (!IsRosa) {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
|
||||
+ addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
|
||||
+ } else {
|
||||
+ addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths);
|
||||
+ }
|
||||
}
|
||||
|
||||
ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const {
|
||||
@@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const {
|
||||
return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker";
|
||||
}
|
||||
if (Triple.isMusl()) {
|
||||
+ if (Triple.getVendor() == llvm::Triple::Rosa)
|
||||
+ return "/system/bin/linker";
|
||||
+
|
||||
std::string ArchName;
|
||||
bool IsArm = false;
|
||||
|
||||
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
|
||||
index 64324a3f8b01..15ce70b68217 100644
|
||||
--- a/clang/tools/clang-installapi/Options.cpp
|
||||
+++ b/clang/tools/clang-installapi/Options.cpp
|
||||
@@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) {
|
||||
FEOpts.FwkPaths = std::move(FrameworkPaths);
|
||||
|
||||
// Add default framework/library paths.
|
||||
- PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"};
|
||||
+ PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"};
|
||||
PathSeq DefaultFrameworkPaths = {"/Library/Frameworks",
|
||||
"/System/Library/Frameworks"};
|
||||
|
||||
`},
|
||||
}
|
||||
4
internal/rosa/llvm_riscv64.go
Normal file
4
internal/rosa/llvm_riscv64.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package rosa
|
||||
|
||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||
var clangPatches []KV
|
||||
@@ -20,9 +20,9 @@ cd "$(mktemp -d)"
|
||||
--disable-dependency-tracking
|
||||
./build.sh
|
||||
./make DESTDIR=/work install check
|
||||
`, pkg.Path(AbsUsrSrc.Append("make"), false, newTar(
|
||||
"https://ftpmirror.gnu.org/gnu/make/make-"+version+".tar.gz",
|
||||
checksum,
|
||||
`, pkg.Path(AbsUsrSrc.Append("make"), false, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/make/make-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
))), version
|
||||
}
|
||||
@@ -78,6 +78,11 @@ type MakeHelper struct {
|
||||
|
||||
var _ Helper = new(MakeHelper)
|
||||
|
||||
// name returns its arguments joined with '-'.
|
||||
func (*MakeHelper) name(name, version string) string {
|
||||
return name + "-" + version
|
||||
}
|
||||
|
||||
// extra returns make and other optional dependencies.
|
||||
func (attr *MakeHelper) extra(flag int) P {
|
||||
extra := P{Make}
|
||||
@@ -171,10 +176,7 @@ func (attr *MakeHelper) script(name string) string {
|
||||
s = "-" + s
|
||||
}
|
||||
if v[1] != "" {
|
||||
if v[0] != "" {
|
||||
s += "="
|
||||
}
|
||||
s += v[1]
|
||||
s += "=" + v[1]
|
||||
}
|
||||
if !yield(s) {
|
||||
return
|
||||
@@ -188,7 +190,7 @@ func (attr *MakeHelper) script(name string) string {
|
||||
|
||||
scriptMake := `
|
||||
make \
|
||||
` + jobsFlagE
|
||||
"-j$(nproc)"`
|
||||
if len(attr.Make) > 0 {
|
||||
scriptMake += " \\\n\t" + strings.Join(attr.Make, " \\\n\t")
|
||||
}
|
||||
@@ -196,7 +198,7 @@ make \
|
||||
|
||||
if !attr.SkipCheck {
|
||||
scriptMake += attr.ScriptCheckEarly + `make \
|
||||
` + jobsFlagE + ` \
|
||||
"-j$(nproc)" \
|
||||
`
|
||||
if len(attr.Check) > 0 {
|
||||
scriptMake += strings.Join(attr.Check, " \\\n\t")
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibglvnd() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.7.0"
|
||||
checksum = "eIQJK2sgFQDHdeFkQO87TrSUaZRFG4y2DrwA8Ut-sGboI59uw1OOiIVqq2AIwnGY"
|
||||
)
|
||||
return t.NewPackage("libglvnd", version, newFromGitLab(
|
||||
"gitlab.freedesktop.org",
|
||||
"glvnd/libglvnd",
|
||||
"v"+version,
|
||||
checksum,
|
||||
), nil, (*MesonHelper)(nil),
|
||||
Binutils, // symbols check fail with llvm nm
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libglvnd] = Metadata{
|
||||
f: Toolchain.newLibglvnd,
|
||||
|
||||
Name: "libglvnd",
|
||||
Description: "The GL Vendor-Neutral Dispatch library",
|
||||
Website: "https://gitlab.freedesktop.org/glvnd/libglvnd",
|
||||
|
||||
ID: 12098,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibdrm() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.4.131"
|
||||
checksum = "riHPSpvTnvCPbR-iT4jt7_X-z4rpwm6oNh9ZN2zP6RBFkFVxBRKmedG4eEXSADIh"
|
||||
)
|
||||
return t.NewPackage("libdrm", version, newFromGitLab(
|
||||
"gitlab.freedesktop.org",
|
||||
"mesa/libdrm",
|
||||
"libdrm-"+version,
|
||||
checksum,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Dintel", "enabled"},
|
||||
},
|
||||
},
|
||||
Binutils, // symbols check fail with llvm nm
|
||||
|
||||
Libpciaccess,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libdrm] = Metadata{
|
||||
f: Toolchain.newLibdrm,
|
||||
|
||||
Name: "libdrm",
|
||||
Description: "a userspace library for accessing the DRM",
|
||||
Website: "https://dri.freedesktop.org/",
|
||||
|
||||
Dependencies: P{
|
||||
Libpciaccess,
|
||||
},
|
||||
|
||||
ID: 1596,
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
func (t Toolchain) newMeson() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.11.0"
|
||||
checksum = "b7oo3U_cklhzsTfsyYsjPGyeEufiS-Pm06JPLzodseS125Ach62ZBly7R6dSDiAc"
|
||||
version = "1.10.2"
|
||||
checksum = "18VmKUVKuXCwtawkYCeYHseC3cKpi86OhnIPaV878wjY0rkXH8XnQwUyymnxFgcl"
|
||||
)
|
||||
return t.New("meson-"+version, 0, []pkg.Artifact{
|
||||
t.Load(Zlib),
|
||||
@@ -23,11 +23,10 @@ python3 setup.py \
|
||||
install \
|
||||
--prefix=/system \
|
||||
--root=/work
|
||||
`, pkg.Path(AbsUsrSrc.Append("meson"), true, newFromGitHubRelease(
|
||||
"mesonbuild/meson",
|
||||
version,
|
||||
"meson-"+version+".tar.gz",
|
||||
checksum,
|
||||
`, pkg.Path(AbsUsrSrc.Append("meson"), true, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/mesonbuild/meson/releases/download/"+
|
||||
version+"/meson-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
))), version
|
||||
}
|
||||
@@ -67,6 +66,11 @@ type MesonHelper struct {
|
||||
|
||||
var _ Helper = new(MesonHelper)
|
||||
|
||||
// name returns its arguments joined with '-'.
|
||||
func (*MesonHelper) name(name, version string) string {
|
||||
return name + "-" + version
|
||||
}
|
||||
|
||||
// extra returns hardcoded meson runtime dependencies.
|
||||
func (*MesonHelper) extra(int) P { return P{Meson} }
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ cp -v lksh /work/system/bin/sh
|
||||
|
||||
mkdir -p /work/bin/
|
||||
ln -vs ../system/bin/sh /work/bin/
|
||||
`, pkg.Path(AbsUsrSrc.Append("mksh"), false, newTar(
|
||||
`, pkg.Path(AbsUsrSrc.Append("mksh"), false, pkg.NewHTTPGetTar(
|
||||
nil,
|
||||
"https://mbsd.evolvis.org/MirOS/dist/mir/mksh/mksh-R"+version+".tgz",
|
||||
checksum,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
))), version
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ func (t Toolchain) newMuslFts() (pkg.Artifact, string) {
|
||||
version = "1.2.7"
|
||||
checksum = "N_p_ZApX3eHt7xoDCw1hLf6XdJOw7ZSx7xPvpvAP0knG2zgU0zeN5w8tt5Pg60XJ"
|
||||
)
|
||||
return t.NewPackage("musl-fts", version, newFromGitHub(
|
||||
"void-linux/musl-fts",
|
||||
"v"+version,
|
||||
checksum,
|
||||
return t.NewPackage("musl-fts", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/void-linux/musl-fts/archive/refs/tags/"+
|
||||
"v"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
"CC=cc -fPIC",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user