internal/pkg: write per-artifact logs
Some checks failed
Test / Flake checks (push) Has been cancelled
Test / Hakurei (race detector) (push) Has been cancelled
Test / Create distribution (push) Has been cancelled
Test / Hakurei (push) Has been cancelled
Test / Sandbox (race detector) (push) Has been cancelled
Test / Sandbox (push) Has been cancelled
Test / ShareFS (push) Has been cancelled

This is currently only used by execArtifact. A later patch will add additional logging facilities.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-03-03 22:47:18 +09:00
parent ea87664a75
commit 7b8df12e97
3 changed files with 125 additions and 8 deletions

View File

@@ -28,15 +28,21 @@ import (
"unsafe"
"hakurei.app/container/check"
"hakurei.app/internal/info"
"hakurei.app/internal/lockedfile"
"hakurei.app/message"
)
const (
// programName is the string identifying this build system.
programName = "internal/pkg"
)
type (
// A Checksum is a SHA-384 checksum computed for a cured [Artifact].
Checksum = [sha512.Size384]byte
// An ID is a unique identifier returned by [Artifact.ID]. This value must
// An ID is a unique identifier returned by [KnownIdent.ID]. This value must
// be deterministically determined ahead of time.
ID Checksum
)
@@ -70,6 +76,80 @@ type common struct {
// Address of underlying [Cache], should be zeroed or made unusable after
// Cure returns and must not be exposed directly.
cache *Cache
// Target [Artifact] encoded identifier.
ids string
// Pathname status was created at.
statusPath *check.Absolute
// File statusHeader and logs are written to.
status *os.File
// Error value during prepareStatus.
statusErr error
}
// statusHeader is the header written to all status files in dirStatus.
var statusHeader = func() string {
s := programName
if v := info.Version(); v != info.FallbackVersion {
s += " " + v
}
s += " (" + runtime.GOARCH + ")"
if name, err := os.Hostname(); err == nil {
s += " on " + name
}
s += "\n\n"
return s
}()
// prepareStatus initialises the status file once.
func (c *common) prepareStatus() error {
if c.statusPath != nil || c.status != nil {
return c.statusErr
}
c.statusPath = c.cache.base.Append(
dirStatus,
c.ids,
)
if c.status, c.statusErr = os.OpenFile(
c.statusPath.String(),
syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY,
0400,
); c.statusErr != nil {
return c.statusErr
}
if _, c.statusErr = c.status.WriteString(statusHeader); c.statusErr != nil {
c.close(&c.statusErr)
}
return c.statusErr
}
// GetStatusWriter returns a [io.Writer] for build logs. The caller must not
// seek this writer before the position it was first returned in.
func (c *common) GetStatusWriter() (io.Writer, error) {
err := c.prepareStatus()
return c.status, err
}
// close closes status and joins its errors with the error referred to by errP.
// If the error referred to by errP is non-nil, the status file is removed from
// the filesystem.
//
// close must be deferred by [Cache.Cure] if common is passed to any Cure
// implementation.
func (c *common) close(errP *error) {
if c.status == nil {
return
}
if err := c.status.Close(); err != nil {
*errP = errors.Join(*errP, err)
}
if *errP != nil {
*errP = errors.Join(*errP, os.Remove(c.statusPath.String()))
}
c.status = nil
}
// TContext is passed to [TrivialArtifact.Cure] and provides information and
@@ -169,7 +249,7 @@ type FContext struct {
}
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up
// via [FContext.Pathname] by a misbehaving [Artifact] implementation.
// via [FContext.GetArtifact] by a misbehaving [Artifact] implementation.
type InvalidLookupError ID
func (e InvalidLookupError) Error() string {
@@ -375,6 +455,9 @@ const (
// dirChecksum is the directory name appended to Cache.base for storing
// artifacts named after their [Checksum].
dirChecksum = "checksum"
// dirStatus is the directory name appended to Cache.base for storing
// artifact metadata and logs named after their [ID].
dirStatus = "status"
// dirWork is the directory name appended to Cache.base for working
// pathnames set up during [Cache.Cure].
@@ -943,8 +1026,9 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
if !errors.Is(err, os.ErrNotExist) {
return
}
id := c.Ident(f)
if c.msg.IsVerbose() {
rn := reportName(f, c.Ident(f))
rn := reportName(f, id)
c.msg.Verbosef("curing %s in memory...", rn)
defer func() {
if err == nil {
@@ -952,7 +1036,7 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
}
}()
}
return f.Cure(&RContext{common{c}})
return f.Cure(&RContext{common{cache: c, ids: Encode(id.Value())}})
}
return
}
@@ -1448,7 +1532,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
if err = c.enterCure(a, curesExempt); err != nil {
return
}
r, err = f.Cure(&RContext{common{c}})
r, err = f.Cure(&RContext{common{cache: c, ids: ids}})
if err == nil {
if checksumPathname == nil || c.IsStrict() {
h := sha512.New384()
@@ -1527,7 +1611,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
t := TContext{
c.base.Append(dirWork, ids),
c.base.Append(dirTemp, ids),
common{c},
common{cache: c, ids: ids},
}
switch ca := a.(type) {
case TrivialArtifact:
@@ -1714,6 +1798,7 @@ func open(
for _, name := range []string{
dirIdentifier,
dirChecksum,
dirStatus,
dirWork,
} {
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&