depot/go/nix/nixdrv/nixdrv.go

355 lines
8.2 KiB
Go

// SPDX-FileCopyrightText: 2023 Luke Granger-Brown <depot@lukegb.com>
//
// 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
}