instantiated: parse instantiated messages
This commit is contained in:
parent
db93b6be6c
commit
1f01e82714
128
instantiated.go
Normal file
128
instantiated.go
Normal file
@ -0,0 +1,128 @@
|
||||
package nixbuild
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
instantiatedPrefix = "instantiated '"
|
||||
instantiatedSeparator = " -> "
|
||||
instantiatedFields = 2
|
||||
|
||||
// a reasonable starting buffer capacity: pretty close to average of test cases
|
||||
instantiatedInitialCap = 1 << 13
|
||||
)
|
||||
|
||||
const (
|
||||
InstantiatedBadFields = iota
|
||||
InstantiatedUnexpectedQuotes
|
||||
InstantiatedNotAbsolute
|
||||
)
|
||||
|
||||
type MalformedInstantiatedError struct {
|
||||
Message string
|
||||
Type int
|
||||
}
|
||||
|
||||
func (m *MalformedInstantiatedError) Error() string {
|
||||
switch m.Type {
|
||||
case InstantiatedBadFields:
|
||||
return "incorrect amount of fields in instantiated message"
|
||||
case InstantiatedUnexpectedQuotes:
|
||||
return "unexpected quotes in final instantiated field"
|
||||
case InstantiatedNotAbsolute:
|
||||
return "instantiated derivation path is not absolute"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MalformedInstantiatedError) Is(err error) bool {
|
||||
var e *MalformedInstantiatedError
|
||||
if !errors.As(err, &e) {
|
||||
return false
|
||||
}
|
||||
if e == nil || m == nil {
|
||||
return e == m
|
||||
}
|
||||
return e.Type == m.Type
|
||||
}
|
||||
|
||||
// DecodeInstantiated interprets the verbose output of `nix build` and returns a slice of all instantiated derivations.
|
||||
func DecodeInstantiated(stderr io.Reader) ([]string, error) {
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
|
||||
instantiated := make([]string, 0, instantiatedInitialCap)
|
||||
for scanner.Scan() {
|
||||
// on error, process is killed by deferred context cancel
|
||||
|
||||
if len(instantiated) == cap(instantiated) {
|
||||
// grow the buffer exponentially to minimise copies
|
||||
instantiated = slices.Grow(instantiated, cap(instantiated)<<1)
|
||||
}
|
||||
|
||||
v := scanner.Text()
|
||||
if !strings.HasPrefix(v, instantiatedPrefix) {
|
||||
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)
|
||||
}
|
||||
|
||||
slices.Sort(instantiated)
|
||||
return slices.Compact(instantiated), scanner.Err()
|
||||
|
||||
}
|
||||
|
||||
// EvalInstantiated evaluates the installable and returns all derivations instantiated during the evaluation.
|
||||
// This relies on verbose output of `nix build` and is certainly not the correct way to do it, but so far it works
|
||||
// significantly better than `nix-store -qR` and there does not appear to be a better way.
|
||||
func EvalInstantiated(ctx context.Context, installable string) ([]string, error) {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(c,
|
||||
"nix", "build", installable,
|
||||
// 'instantiated' messages are only emitted when actually evaluating something
|
||||
"--option", "eval-cache", "false",
|
||||
// do not actually build anything
|
||||
"--dry-run",
|
||||
// increase verbosity so nix outputs 'instantiated' messages
|
||||
"-Lvvv",
|
||||
)
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
// have finalizer take care of the pipe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instantiated, decodeErr := DecodeInstantiated(stderr)
|
||||
waitErr := cmd.Wait()
|
||||
return instantiated, errors.Join(decodeErr, waitErr)
|
||||
}
|
6290
instantiated_sample_getchoo_atlas_test.go
Normal file
6290
instantiated_sample_getchoo_atlas_test.go
Normal file
File diff suppressed because it is too large
Load Diff
10741
instantiated_sample_getchoo_glados_test.go
Normal file
10741
instantiated_sample_getchoo_glados_test.go
Normal file
File diff suppressed because it is too large
Load Diff
10697
instantiated_sample_pluiedev_pappardelle_test.go
Normal file
10697
instantiated_sample_pluiedev_pappardelle_test.go
Normal file
File diff suppressed because it is too large
Load Diff
121
instantiated_test.go
Normal file
121
instantiated_test.go
Normal file
@ -0,0 +1,121 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
)
|
||||
|
||||
func TestDecodeInstantiated(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
out string
|
||||
want []string
|
||||
wantErr error
|
||||
}{
|
||||
{"bad fields", segmentPrefix + `instantiated 'config.sub-948ae97' ` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedBadFields}},
|
||||
{"unexpected quotes left", segmentPrefix + `instantiated 'config.sub-948ae97' -> /n'` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"unexpected quotes right", segmentPrefix + `instantiated 'config.sub-948ae97' -> '/n` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"unexpected quotes short", segmentPrefix + `instantiated 'config.sub-948ae97' -> ''` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"not absolute", segmentPrefix + `instantiated 'config.sub-948ae97' -> ' '` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedNotAbsolute}},
|
||||
{"good segment", segmentPrefix + segmentBody + segmentSuffix, []string{
|
||||
"/nix/store/3zilrlmq7r6rpzfd94mwss32b62yinj5-bootstrap-stage0-stdenv-linux.drv",
|
||||
"/nix/store/7yfwy95p6lcdpljdajs5aw10h6q0sfx0-update-autotools-gnu-config-scripts-hook.drv",
|
||||
"/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv",
|
||||
"/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv",
|
||||
"/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv",
|
||||
"/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv",
|
||||
}, nil},
|
||||
|
||||
{"getchoo atlas", getchooAtlasOut, getchooAtlas, nil},
|
||||
{"getchoo glados", getchooGladosOut, getchooGlados, nil},
|
||||
{"pluiedev pappardelle", pluiedevPappardelleOut, pluiedevPappardelle, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
stderr := strings.NewReader(tc.out)
|
||||
got, err := nixbuild.DecodeInstantiated(stderr)
|
||||
if !errors.Is(err, tc.wantErr) {
|
||||
t.Fatalf("DecodeInstantiated: error = %v, want %v", err, tc.wantErr)
|
||||
}
|
||||
if tc.wantErr != nil {
|
||||
t.Logf("DecodeInstantiated: error = %v", err)
|
||||
return
|
||||
}
|
||||
if !slices.Equal(got, tc.want) {
|
||||
t.Errorf("DecodeInstantiated: %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("errors", func(t *testing.T) {
|
||||
t.Run("bad type", func(t *testing.T) {
|
||||
badErr := errors.New("")
|
||||
if errors.Is(new(nixbuild.MalformedInstantiatedError), badErr) {
|
||||
t.Error("unexpected MalformedInstantiatedError equivalence")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
if errors.Is(new(nixbuild.MalformedInstantiatedError), (*nixbuild.MalformedInstantiatedError)(nil)) {
|
||||
t.Error("unexpected MalformedInstantiatedError equivalence")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
segmentPrefix = `evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/development/libraries/zlib/default.nix'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/fetchurl/boot.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'zlib-1.3.1.tar.gz' -> '/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to the store...
|
||||
performing daemon worker op: 7
|
||||
copied '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
copied source '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' -> '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/by-name/gn/gnu-config/package.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'config.guess-948ae97' -> '/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv'
|
||||
performing daemon worker op: 7
|
||||
`
|
||||
|
||||
segmentBody = `instantiated 'config.sub-948ae97' -> '/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv'`
|
||||
|
||||
segmentSuffix = `
|
||||
performing daemon worker op: 7
|
||||
instantiated 'gnu-config-2024-01-01' -> '/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'bootstrap-stage0-stdenv-linux' -> '/nix/store/3zilrlmq7r6rpzfd94mwss32b62yinj5-bootstrap-stage0-stdenv-linux.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'update-autotools-gnu-config-scripts-hook' -> '/nix/store/7yfwy95p6lcdpljdajs5aw10h6q0sfx0-update-autotools-gnu-config-scripts-hook.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' to the store...`
|
||||
)
|
||||
|
||||
func BenchmarkDecodeInstantiated(b *testing.B) {
|
||||
stderr := strings.NewReader(pluiedevPappardelleOut)
|
||||
var v []string
|
||||
for i := 0; i < b.N; i++ {
|
||||
v, _ = nixbuild.DecodeInstantiated(stderr)
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeInstantiatedCopy(b *testing.B) {
|
||||
stderr := strings.NewReader(getchooAtlasOut)
|
||||
var v []string
|
||||
for i := 0; i < b.N; i++ {
|
||||
v, _ = nixbuild.DecodeInstantiated(stderr)
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
}
|
37432
testdata/getchoo_atlas
vendored
Normal file
37432
testdata/getchoo_atlas
vendored
Normal file
File diff suppressed because it is too large
Load Diff
84832
testdata/getchoo_glados
vendored
Normal file
84832
testdata/getchoo_glados
vendored
Normal file
File diff suppressed because it is too large
Load Diff
108599
testdata/pluiedev_pappardelle
vendored
Normal file
108599
testdata/pluiedev_pappardelle
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user