internal/pkg: write per-artifact logs
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m30s
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m30s
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:
@@ -418,6 +418,12 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
z.Hostname = "cure-net"
|
z.Hostname = "cure-net"
|
||||||
}
|
}
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
|
|
||||||
|
var status io.Writer
|
||||||
|
if status, err = f.GetStatusWriter(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if msg := f.GetMessage(); msg.IsVerbose() {
|
if msg := f.GetMessage(); msg.IsVerbose() {
|
||||||
var stdout, stderr io.ReadCloser
|
var stdout, stderr io.ReadCloser
|
||||||
if stdout, err = z.StdoutPipe(); err != nil {
|
if stdout, err = z.StdoutPipe(); err != nil {
|
||||||
@@ -434,10 +440,29 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
bw := f.cache.getWriter(status)
|
||||||
|
defer func() {
|
||||||
|
flushErr := bw.Flush()
|
||||||
|
if err == nil {
|
||||||
|
err = flushErr
|
||||||
|
}
|
||||||
|
f.cache.putWriter(bw)
|
||||||
|
}()
|
||||||
|
|
||||||
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
||||||
go scanVerbose(msg, cancel, stdoutDone, "("+a.name+":1)", stdout)
|
go scanVerbose(
|
||||||
go scanVerbose(msg, cancel, stderrDone, "("+a.name+":2)", stderr)
|
msg, cancel, stdoutDone,
|
||||||
|
"("+a.name+":1)",
|
||||||
|
io.TeeReader(stdout, bw),
|
||||||
|
)
|
||||||
|
go scanVerbose(
|
||||||
|
msg, cancel, stderrDone,
|
||||||
|
"("+a.name+":2)",
|
||||||
|
io.TeeReader(stderr, bw),
|
||||||
|
)
|
||||||
defer func() { <-stdoutDone; <-stderrDone }()
|
defer func() { <-stdoutDone; <-stderrDone }()
|
||||||
|
} else {
|
||||||
|
z.Stdout, z.Stderr = status, status
|
||||||
}
|
}
|
||||||
|
|
||||||
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
||||||
|
|||||||
@@ -28,15 +28,21 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/lockedfile"
|
"hakurei.app/internal/lockedfile"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// programName is the string identifying this build system.
|
||||||
|
programName = "internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A Checksum is a SHA-384 checksum computed for a cured [Artifact].
|
// A Checksum is a SHA-384 checksum computed for a cured [Artifact].
|
||||||
Checksum = [sha512.Size384]byte
|
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.
|
// be deterministically determined ahead of time.
|
||||||
ID Checksum
|
ID Checksum
|
||||||
)
|
)
|
||||||
@@ -70,6 +76,80 @@ type common struct {
|
|||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||||
// Cure returns and must not be exposed directly.
|
// Cure returns and must not be exposed directly.
|
||||||
cache *Cache
|
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
|
// 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
|
// 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
|
type InvalidLookupError ID
|
||||||
|
|
||||||
func (e InvalidLookupError) Error() string {
|
func (e InvalidLookupError) Error() string {
|
||||||
@@ -375,6 +455,9 @@ const (
|
|||||||
// dirChecksum is the directory name appended to Cache.base for storing
|
// dirChecksum is the directory name appended to Cache.base for storing
|
||||||
// artifacts named after their [Checksum].
|
// artifacts named after their [Checksum].
|
||||||
dirChecksum = "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
|
// dirWork is the directory name appended to Cache.base for working
|
||||||
// pathnames set up during [Cache.Cure].
|
// 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) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
id := c.Ident(f)
|
||||||
if c.msg.IsVerbose() {
|
if c.msg.IsVerbose() {
|
||||||
rn := reportName(f, c.Ident(f))
|
rn := reportName(f, id)
|
||||||
c.msg.Verbosef("curing %s in memory...", rn)
|
c.msg.Verbosef("curing %s in memory...", rn)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
@@ -1448,7 +1532,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
if err = c.enterCure(a, curesExempt); err != nil {
|
if err = c.enterCure(a, curesExempt); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r, err = f.Cure(&RContext{common{c}})
|
r, err = f.Cure(&RContext{common{cache: c, ids: ids}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if checksumPathname == nil || c.IsStrict() {
|
if checksumPathname == nil || c.IsStrict() {
|
||||||
h := sha512.New384()
|
h := sha512.New384()
|
||||||
@@ -1527,7 +1611,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
t := TContext{
|
t := TContext{
|
||||||
c.base.Append(dirWork, ids),
|
c.base.Append(dirWork, ids),
|
||||||
c.base.Append(dirTemp, ids),
|
c.base.Append(dirTemp, ids),
|
||||||
common{c},
|
common{cache: c, ids: ids},
|
||||||
}
|
}
|
||||||
switch ca := a.(type) {
|
switch ca := a.(type) {
|
||||||
case TrivialArtifact:
|
case TrivialArtifact:
|
||||||
@@ -1714,6 +1798,7 @@ func open(
|
|||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
dirIdentifier,
|
dirIdentifier,
|
||||||
dirChecksum,
|
dirChecksum,
|
||||||
|
dirStatus,
|
||||||
dirWork,
|
dirWork,
|
||||||
} {
|
} {
|
||||||
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
|
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
|
||||||
|
|||||||
@@ -314,6 +314,11 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destroy non-deterministic status files
|
||||||
|
if err := os.RemoveAll(base.Append("status").String()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
var checksum pkg.Checksum
|
var checksum pkg.Checksum
|
||||||
if err := pkg.HashDir(&checksum, base); err != nil {
|
if err := pkg.HashDir(&checksum, base); err != nil {
|
||||||
t.Fatalf("HashDir: error = %v", err)
|
t.Fatalf("HashDir: error = %v", err)
|
||||||
@@ -382,6 +387,9 @@ func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) {
|
|||||||
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
|
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
|
||||||
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
|
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
|
||||||
} else if checksum != makeChecksumH(step.checksum) {
|
} else if checksum != makeChecksumH(step.checksum) {
|
||||||
|
if checksum == (unique.Handle[pkg.Checksum]{}) {
|
||||||
|
checksum = unique.Make(pkg.Checksum{})
|
||||||
|
}
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Cure: checksum = %s, want %s",
|
"Cure: checksum = %s, want %s",
|
||||||
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),
|
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),
|
||||||
|
|||||||
Reference in New Issue
Block a user