// SPDX-FileCopyrightText: 2023 Luke Granger-Brown // // SPDX-License-Identifier: Apache-2.0 // Package nixdrv holds basic data types for describing Nix derivations. package nixdrv import ( "bufio" "bytes" "fmt" "strings" ) type Output struct { Path string HashAlgorithm string Hash string } type InputDerivation struct { Path string Outputs []string } type BasicDerivation struct { Outputs map[string]Output InputSrcs map[string]bool // PathSet Platform string Builder string Args []string Env map[string]string InputDerivations []InputDerivation } type Derivation struct { BasicDerivation InputDerivations []InputDerivation } func (drv *BasicDerivation) Clone() *BasicDerivation { o := &BasicDerivation{ Outputs: map[string]Output{}, InputSrcs: map[string]bool{}, Platform: drv.Platform, Builder: drv.Builder, Args: drv.Args, Env: map[string]string{}, } for k, v := range drv.Outputs { o.Outputs[k] = v } for k, v := range drv.InputSrcs { o.InputSrcs[k] = v } for k, v := range drv.Env { o.Env[k] = v } return o } func (drv *Derivation) Clone() *Derivation { return &Derivation{ BasicDerivation: *drv.BasicDerivation.Clone(), InputDerivations: drv.InputDerivations, } } type Resolver interface { LoadDerivation(path string) (*Derivation, error) } func (drv *Derivation) ToBasicDerivation(r Resolver) (*BasicDerivation, error) { o := drv.BasicDerivation.Clone() for _, inp := range drv.InputDerivations { inpDrv, err := r.LoadDerivation(inp.Path) if err != nil { return nil, fmt.Errorf("resolving %v: %w", inp.Path, err) } for _, inpOut := range inp.Outputs { inpDrvOut, ok := inpDrv.Outputs[inpOut] if !ok { return nil, fmt.Errorf("input derivation %v has no output %v", inp.Path, inpOut) } o.InputSrcs[inpDrvOut.Path] = true } } return o, nil } func expect(f *bufio.Reader, s string) error { buf := make([]byte, len(s)) _, err := f.Read(buf) if err != nil { return err } if !bytes.Equal(buf, []byte(s)) { return fmt.Errorf("expected %q; got %q", s, string(buf)) } return nil } func expectChar(f *bufio.Reader, c byte) error { b, err := f.ReadByte() switch { case err != nil: return fmt.Errorf("reading char: %w", err) case b != c: return fmt.Errorf("expected char '%c', got '%c'", c, b) } return nil } func readList[T any](f *bufio.Reader, readElement func(f *bufio.Reader) (T, error)) ([]T, error) { if err := expectChar(f, '['); err != nil { return nil, fmt.Errorf("list open: %w", err) } b, err := f.ReadByte() if err != nil { return nil, fmt.Errorf("list first char: %w", err) } if b == ']' { // Empty list. return nil, nil } if err := f.UnreadByte(); err != nil { return nil, fmt.Errorf("unreading: %w", err) } var l []T loop: for { t, err := readElement(f) if err != nil { return nil, fmt.Errorf("list element: %w", err) } l = append(l, t) b, err := f.ReadByte() switch { case err != nil: return nil, fmt.Errorf("reading list element or end of list: %w", err) case b == ']': break loop case b == ',': continue default: return nil, fmt.Errorf("expecting list element or end of list: got '%c'", b) } } return l, nil } func readStrings(f *bufio.Reader) ([]string, error) { return readList(f, readString) } func readString(f *bufio.Reader) (string, error) { if err := expectChar(f, '"'); err != nil { return "", fmt.Errorf("expected string opening '\"': %w", err) } var s strings.Builder loop: for { b, err := f.ReadByte() if err != nil { return "", fmt.Errorf("reading string: %w", err) } switch b { case '"': break loop case '\\': b, err := f.ReadByte() if err != nil { return "", fmt.Errorf("reading string: %w", err) } switch b { case 'n': s.WriteByte('\n') case 'r': s.WriteByte('\r') case 't': s.WriteByte('\t') default: s.WriteByte(b) } continue } s.WriteByte(b) } return s.String(), nil } func Load(f *bufio.Reader) (*Derivation, error) { drv := &Derivation{} if err := expect(f, "Derive("); err != nil { return nil, err } type outputWithName struct { Name string Output } outputList, err := readList(f, func(f *bufio.Reader) (outputWithName, error) { var ( empT, o outputWithName err error ) if err := expectChar(f, '('); err != nil { return empT, fmt.Errorf("output open: %w", err) } if o.Name, err = readString(f); err != nil { return empT, fmt.Errorf("output name: %w", err) } if err := expectChar(f, ','); err != nil { return empT, fmt.Errorf(", after output name: %w", err) } if o.Path, err = readString(f); err != nil { return empT, fmt.Errorf("output path: %w", err) } if err := expectChar(f, ','); err != nil { return empT, fmt.Errorf(", after output path: %w", err) } if o.HashAlgorithm, err = readString(f); err != nil { return empT, fmt.Errorf("output hash algorithm: %w", err) } if err := expectChar(f, ','); err != nil { return empT, fmt.Errorf(", after output hash algorithm: %w", err) } if o.Hash, err = readString(f); err != nil { return empT, fmt.Errorf("output hash: %w", err) } if err := expectChar(f, ')'); err != nil { return empT, fmt.Errorf("output close: %w", err) } return o, nil }) if err != nil { return nil, fmt.Errorf("reading outputs: %w", err) } drv.Outputs = make(map[string]Output) for _, own := range outputList { drv.Outputs[own.Name] = own.Output } if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after outputs list: %w", err) } drv.InputDerivations, err = readList(f, func(f *bufio.Reader) (InputDerivation, error) { var ( empT, id InputDerivation err error ) if err := expectChar(f, '('); err != nil { return empT, fmt.Errorf("input derivation open: %w", err) } if id.Path, err = readString(f); err != nil { return empT, fmt.Errorf("input derivation path: %w", err) } if err := expectChar(f, ','); err != nil { return empT, fmt.Errorf(", after input derivation path: %w", err) } if id.Outputs, err = readStrings(f); err != nil { return empT, fmt.Errorf("input derivation outputs: %w", err) } if err := expectChar(f, ')'); err != nil { return empT, fmt.Errorf("input derivation close: %w", err) } return id, nil }) if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after input derivations list: %w", err) } inputSrcsList, err := readStrings(f) if err != nil { return nil, fmt.Errorf("reading input srcs list: %w", err) } drv.InputSrcs = make(map[string]bool) for _, k := range inputSrcsList { drv.InputSrcs[k] = true } if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after input srcs list: %w", err) } if drv.Platform, err = readString(f); err != nil { return nil, fmt.Errorf("reading platform: %w", err) } if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after platform: %w", err) } if drv.Builder, err = readString(f); err != nil { return nil, fmt.Errorf("reading builder: %w", err) } if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after builder: %w", err) } if drv.Args, err = readStrings(f); err != nil { return nil, fmt.Errorf("reading builder args: %w", err) } if err := expectChar(f, ','); err != nil { return nil, fmt.Errorf(", after builder args: %w", err) } type keyValue struct { key, value string } envList, err := readList(f, func(f *bufio.Reader) (keyValue, error) { var ( empT, kv keyValue err error ) if err := expectChar(f, '('); err != nil { return empT, fmt.Errorf("key value open: %w", err) } if kv.key, err = readString(f); err != nil { return empT, fmt.Errorf("key: %w", err) } if err := expectChar(f, ','); err != nil { return empT, fmt.Errorf(", after key: %w", err) } if kv.value, err = readString(f); err != nil { return empT, fmt.Errorf("value: %w", err) } if err := expectChar(f, ')'); err != nil { return empT, fmt.Errorf("key value close: %w", err) } return kv, nil }) drv.Env = make(map[string]string) for _, kv := range envList { drv.Env[kv.key] = kv.value } if err := expectChar(f, ')'); err != nil { return nil, fmt.Errorf("closing brace: %w", err) } return drv, nil }