instantiated: implement decoding iterator
This commit is contained in:
parent
edac902c7f
commit
93f447163d
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@ -54,46 +55,74 @@ func (m *MalformedInstantiatedError) Is(err error) bool {
|
|||||||
return e.Type == m.Type
|
return e.Type == m.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstantiatedDecoder interprets the verbose output of `nix build` and collects instantiated derivations.
|
||||||
|
type InstantiatedDecoder struct {
|
||||||
|
err error
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the first error encountered by the InstantiatedDecoder.
|
||||||
|
func (d *InstantiatedDecoder) Err() error { return errors.Join(d.err, d.scanner.Err()) }
|
||||||
|
|
||||||
|
// NewInstantiatedDecoder returns a [InstantiatedDecoder] struct for collecting derivations from r.
|
||||||
|
func NewInstantiatedDecoder(r io.Reader) *InstantiatedDecoder {
|
||||||
|
return &InstantiatedDecoder{scanner: bufio.NewScanner(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drv returns a non-reusable iterator over instantiated derivations collected from [io.Reader].
|
||||||
|
func (d *InstantiatedDecoder) Drv() iter.Seq[string] {
|
||||||
|
return func(yield func(string) bool) {
|
||||||
|
for d.scanner.Scan() {
|
||||||
|
v := d.scanner.Text()
|
||||||
|
if !strings.HasPrefix(v, instantiatedPrefix) {
|
||||||
|
// write skipped lines to stderr
|
||||||
|
if Stderr != nil {
|
||||||
|
_, _ = Stderr.Write([]byte(v + "\n"))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.SplitN(v, instantiatedSeparator, instantiatedFields)
|
||||||
|
if len(fields) != instantiatedFields {
|
||||||
|
// no more than a single -> is expected
|
||||||
|
d.err = &MalformedInstantiatedError{v, InstantiatedBadFields}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// very basic validation here: the output format is not fully understood
|
||||||
|
if len(fields[1]) < 3 || fields[1][0] != '\'' || fields[1][len(fields[1])-1] != '\'' {
|
||||||
|
d.err = &MalformedInstantiatedError{v, InstantiatedUnexpectedQuotes}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
drv := fields[1][1 : len(fields[1])-1]
|
||||||
|
|
||||||
|
if !path.IsAbs(drv) {
|
||||||
|
d.err = &MalformedInstantiatedError{v, InstantiatedNotAbsolute}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(drv) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeInstantiated interprets the verbose output of `nix build` and returns a slice of all instantiated derivations.
|
// DecodeInstantiated interprets the verbose output of `nix build` and returns a slice of all instantiated derivations.
|
||||||
func DecodeInstantiated(stderr io.Reader) ([]string, error) {
|
func DecodeInstantiated(r io.Reader) ([]string, error) {
|
||||||
scanner := bufio.NewScanner(stderr)
|
decoder := NewInstantiatedDecoder(r)
|
||||||
|
|
||||||
instantiated := make([]string, 0, instantiatedInitialCap)
|
instantiated := make([]string, 0, instantiatedInitialCap)
|
||||||
for scanner.Scan() {
|
for drv := range decoder.Drv() {
|
||||||
// on error, process is killed by deferred context cancel
|
|
||||||
|
|
||||||
if len(instantiated) == cap(instantiated) {
|
if len(instantiated) == cap(instantiated) {
|
||||||
// grow the buffer exponentially to minimise copies
|
// grow the buffer exponentially to minimise copies
|
||||||
instantiated = slices.Grow(instantiated, cap(instantiated)<<1)
|
instantiated = slices.Grow(instantiated, cap(instantiated)<<1)
|
||||||
}
|
}
|
||||||
|
|
||||||
v := scanner.Text()
|
|
||||||
if !strings.HasPrefix(v, instantiatedPrefix) {
|
|
||||||
if Stderr != nil {
|
|
||||||
_, _ = Stderr.Write([]byte(v + "\n"))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f := strings.SplitN(v, instantiatedSeparator, instantiatedFields)
|
|
||||||
if len(f) != instantiatedFields {
|
|
||||||
return nil, &MalformedInstantiatedError{v, InstantiatedBadFields}
|
|
||||||
}
|
|
||||||
|
|
||||||
// very basic validation here: the output format is not fully understood
|
|
||||||
if len(f[1]) < 3 || f[1][0] != '\'' || f[1][len(f[1])-1] != '\'' {
|
|
||||||
return nil, &MalformedInstantiatedError{v, InstantiatedUnexpectedQuotes}
|
|
||||||
}
|
|
||||||
drv := f[1][1 : len(f[1])-1]
|
|
||||||
|
|
||||||
if !path.IsAbs(drv) {
|
|
||||||
return nil, &MalformedInstantiatedError{v, InstantiatedNotAbsolute}
|
|
||||||
}
|
|
||||||
|
|
||||||
instantiated = append(instantiated, drv)
|
instantiated = append(instantiated, drv)
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.Sort(instantiated)
|
slices.Sort(instantiated)
|
||||||
return slices.Compact(instantiated), scanner.Err()
|
return slices.Compact(instantiated), decoder.Err()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,29 @@ func TestDecodeInstantiated(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("stop early", func(t *testing.T) {
|
||||||
|
want := []string{
|
||||||
|
"/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv",
|
||||||
|
"/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv",
|
||||||
|
"/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv",
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := nixbuild.NewInstantiatedDecoder(strings.NewReader(segmentPrefix + segmentBody + segmentSuffix))
|
||||||
|
counter := 3
|
||||||
|
got := make([]string, 0, counter)
|
||||||
|
for drv := range decoder.Drv() {
|
||||||
|
got = append(got, drv)
|
||||||
|
counter--
|
||||||
|
if counter == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Errorf("Drv: %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("errors", func(t *testing.T) {
|
t.Run("errors", func(t *testing.T) {
|
||||||
t.Run("bad type", func(t *testing.T) {
|
t.Run("bad type", func(t *testing.T) {
|
||||||
badErr := errors.New("")
|
badErr := errors.New("")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user