aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/pkg.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-03 22:47:18 +0900
committerOphestra <cat@gensokyo.uk>2026-03-04 17:27:54 +0900
commit94e3debc632b6f3f309218395339ca0e9ba754b7 (patch)
treef975d1274d186c19fb3b5f4047a6e1d894720fbb /internal/pkg/pkg.go
parentea87664a759991a14c6891d53a488e3b7af26ee8 (diff)
internal/pkg: write per-artifact logs
This is currently only used by execArtifact. A later patch will add additional logging facilities. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/pkg.go')
-rw-r--r--internal/pkg/pkg.go85
1 files changed, 81 insertions, 4 deletions
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index 5f6f15d2..61ec511c 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -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
)
@@ -81,20 +87,75 @@ type TContext struct {
// Populated during [Cache.Cure].
work, temp *check.Absolute
+ // 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
+
common
}
+// 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 (t *TContext) prepareStatus() error {
+ if t.statusPath != nil || t.status != nil {
+ return t.statusErr
+ }
+
+ t.statusPath = t.cache.base.Append(
+ dirStatus,
+ t.ids,
+ )
+ if t.status, t.statusErr = os.OpenFile(
+ t.statusPath.String(),
+ syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY,
+ 0400,
+ ); t.statusErr != nil {
+ return t.statusErr
+ }
+
+ _, t.statusErr = t.status.WriteString(statusHeader)
+ return t.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 (t *TContext) GetStatusWriter() (io.Writer, error) {
+ err := t.prepareStatus()
+ return t.status, err
+}
+
// destroy destroys the temporary directory and joins its errors with the error
// referred to by errP. If the error referred to by errP is non-nil, the work
// directory is removed similarly. [Cache] is responsible for making sure work
// is never left behind for a successful [Cache.Cure].
//
+// If implementation had requested status, it is closed with error joined 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.
+//
// destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure
// implementation. It should not be called prior to that point.
func (t *TContext) destroy(errP *error) {
if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr)
- return
}
if *errP != nil {
@@ -106,6 +167,16 @@ func (t *TContext) destroy(errP *error) {
*errP = nil
}
}
+
+ if t.status != nil {
+ if err := t.status.Close(); err != nil {
+ *errP = errors.Join(*errP, err)
+ }
+ if *errP != nil {
+ *errP = errors.Join(*errP, os.Remove(t.statusPath.String()))
+ }
+ t.status = nil
+ }
}
// Unwrap returns the underlying [context.Context].
@@ -169,7 +240,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 +446,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 +1017,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 {
@@ -1527,6 +1602,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
t := TContext{
c.base.Append(dirWork, ids),
c.base.Append(dirTemp, ids),
+ ids, nil, nil, nil,
common{c},
}
switch ca := a.(type) {
@@ -1714,6 +1790,7 @@ func open(
for _, name := range []string{
dirIdentifier,
dirChecksum,
+ dirStatus,
dirWork,
} {
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&