derivation: parse nix derivation show

This time it's JSON, and actual intended behaviour.
This commit is contained in:
Yonah 2025-07-15 01:51:55 +09:00
parent 68ae0efe19
commit 6aafccce4c
Signed by: yonah
SSH Key Fingerprint: SHA256:vnQvK8+XXH9Tbni2AV1a/8qdVK/zPcXw52GM0ruQvwA
4 changed files with 132 additions and 12 deletions

View File

@ -2,9 +2,9 @@ package nixbuild
import (
"context"
"errors"
"io"
"iter"
"strings"
)
// Build builds all entries yielded by installables.
@ -32,18 +32,11 @@ func Build(ctx context.Context, installables iter.Seq[string]) error {
return err
}
for drv := range installables {
if strings.HasSuffix(drv, ".drv") {
// this is just what nix requires now :c
drv += "^*"
}
if _, err := iw.Write([]byte(drv + "\n")); err != nil {
return err
}
if _, err := WriteStdin(iw, installables); err != nil {
return errors.Join(err, cmd.Wait())
}
if err := iw.Close(); err != nil {
return err
return errors.Join(err, cmd.Wait())
}
return cmd.Wait()

View File

@ -42,7 +42,20 @@ func main() {
Flag(&flagVerbose, "v", command.BoolFlag(false), "Connect nix stderr").
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
c.Command("build", "Build a list of installables", func(args []string) error {
c.Command("show", command.UsageInternal, func(args []string) error {
if len(args) < 1 {
return commandHandlerError("usage requires at least 1 argument")
}
if drv, err := nixbuild.DerivationShow(ctx, slices.Values(args)); err != nil {
return commandHandlerError(fmt.Sprintf("cannot show: %v", err))
} else {
log.Printf("got %d derivations:\n%#v", len(drv), drv)
return nil
}
})
c.Command("build", command.UsageInternal, func(args []string) error {
if len(args) < 1 {
return commandHandlerError("build requires at least 1 argument")
}

88
derivation.go Normal file
View File

@ -0,0 +1,88 @@
package nixbuild
import (
"context"
"encoding/json"
"errors"
"io"
"iter"
)
type (
// DerivationMap is the output of `nix derivation show`.
DerivationMap map[string]Derivation
// Derivation is a description of a derivation.
Derivation struct {
// Args are arguments passed to Builder.
Args []string `json:"args"`
// Builder is the store path of the program that builds the [Derivation].
Builder string `json:"builder"`
// System is the value of pkgs.system during evaluation.
System string `json:"system"`
// Name is the user-facing name of a derivation, as seen in the nix store path suffix.
Name string `json:"name"`
Environment json.RawMessage `json:"env"`
InputDerivations InputDerivationsMap `json:"inputDrvs"`
InputSources []string `json:"inputSrcs"`
Outputs OutputsMap `json:"outputs"`
}
// OutputsMap is an output name to [Output] map.
OutputsMap map[string]Output
// Output is an output of a [Derivation].
Output struct {
Path string `json:"path"`
}
// InputDerivationsMap is a store path to metadata map.
InputDerivationsMap map[string]InputDerivation
// InputDerivation contains input derivation metadata.
InputDerivation struct {
DynamicOutputs json.RawMessage `json:"dynamicOutputs"`
Outputs []string `json:"outputs"`
}
)
// DerivationShow returns a [DerivationMap] describing all entries yielded by installables.
func DerivationShow(ctx context.Context, installables iter.Seq[string]) (DerivationMap, error) {
c, cancel := context.WithCancel(ctx)
defer cancel()
cmd := Nix(c, CommandDerivation, CommandDerivationShow,
FlagStdin)
ir, iw := io.Pipe()
cmd.Stdin = ir
or, ow := io.Pipe()
cmd.Stdout = ow
if Stderr != nil {
cmd.Stderr = Stderr
}
cmd.Cancel = func() error { _ = ow.Close(); return cmd.Process.Kill() }
if err := cmd.Start(); err != nil {
return nil, err
}
done := make(chan error)
var v DerivationMap
go func() { done <- json.NewDecoder(or).Decode(&v) }()
if _, err := WriteStdin(iw, installables); err != nil {
return nil, errors.Join(err, cmd.Wait())
}
if err := iw.Close(); err != nil {
return nil, errors.Join(err, cmd.Wait())
}
if err := cmd.Wait(); err != nil {
// deferred cancel closes pipe
return nil, err
}
err := <-done
return v, err
}

View File

@ -2,12 +2,21 @@ package nixbuild
import (
"context"
"io"
"iter"
"os/exec"
"strings"
)
const (
// CommandBuild build a derivation or fetch a store path
CommandBuild = "build"
// CommandDerivation work with derivations, Nix's notion of a build plan.
CommandDerivation = "derivation"
// CommandDerivationAdd add a store derivation
CommandDerivationAdd = "add"
// CommandDerivationShow show the contents of a store derivation
CommandDerivationShow = "show"
// FlagDryRun show what this command would do without doing it.
FlagDryRun = "--dry-run"
@ -86,3 +95,20 @@ const (
// NixOSInstallable returns the nixos installable for a given flake and host.
func NixOSInstallable(flake, host string) string { return flake + nixosSuffix0 + host + nixosSuffix1 }
// WriteStdin writes installables for a nix process running with [FlagStdin].
func WriteStdin(w io.Writer, installables iter.Seq[string]) (int, error) {
var count int
for drv := range installables {
if strings.HasSuffix(drv, ".drv") {
// this is just what nix requires now :c
drv += "^*"
}
n, err := w.Write([]byte(drv + "\n"))
count += n
if err != nil {
return count, err
}
}
return count, nil
}