355 lines
8.2 KiB
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
|
|
}
|