tvl-depot/tools/depot-scanner/main.go
zseri 536f7c134a feat(depot-scanner): forward non-processed stderr output
previously, depot-scanner swallowed/ignored all non-processed
stderr output of nix-instantiate, which makes diagnosing
failures of nix-instantiate (e.g. failed with exit status 1)
difficult. This commit fixes that by always forwarding
the remaining stderr messages.

Example previous error message:

  panic: nix-instantiate failed: exit status 1

  goroutine 1 [running]:
  main.main()
	/nix/store/8vb2j13bd7j5ipl7dhsnwvgr7nrrsqsi-main.go:160 +0xeb4

Example new error message:

  nix-inst> error: unrecognised flag '--trace-file-access'
  nix-inst> Try '/run/current-system/sw/bin/nix-instantiate --help' for more information.
  panic: nix-instantiate failed: exit status 1

  goroutine 1 [running]:
  main.main()
	/nix/store/qy7v79a3harddirzmc0432vbzqhyf91i-main.go:165 +0xeb4

Change-Id: I666f3490fc648f77a5384b95edd74f6115f7920d
Reviewed-on: https://cl.tvl.fyi/c/depot/+/4553
Tested-by: BuildkiteCI
Reviewed-by: tazjin <mail@tazj.in>
2021-12-25 05:19:14 +00:00

227 lines
5.5 KiB
Go

package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
pb "code.tvl.fyi/tools/depot-scanner/proto"
)
var nixInstantiatePath = flag.String("nix-bin", "/run/current-system/sw/bin/nix-instantiate", "path to nix-instantiate")
var depotRoot = flag.String("depot", envOr("DEPOT_ROOT", "/depot/"), "path to tvl.fyi depot at current canon")
var nixStoreRoot = flag.String("store-path", "/nix/store/", "prefix for all valid nix store paths")
var modeFlag = flag.String("mode", modeArchive, "operation mode. valid values: tar, print")
var onlyFlag = flag.String("only", "", "only enable the listed output types, comma separated. valid values: DEPOT, STORE, CORE, UNKNOWN")
var relativeFlag = flag.Bool("relpath", false, "when printing paths, print them relative to the root of their path type")
const (
modeArchive = "tar"
modePrint = "print"
)
const (
// String that identifies a path as belonging to nix corepkgs.
corePkgsString = "/share/nix/corepkgs/"
depotTraceString = "trace: depot-scan: "
)
type fileScanType int
const (
unknownPath fileScanType = iota
depotPath
nixStorePath
corePkgsPath
)
func launchNix(attr string) (*exec.Cmd, io.ReadCloser, io.ReadCloser, error) {
cmd := exec.Command(*nixInstantiatePath, "--trace-file-access", "-A", attr)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
stdout.Close()
return nil, nil, nil, err
}
err = cmd.Start()
if err != nil {
stdout.Close()
stderr.Close()
return nil, nil, nil, err
}
return cmd, stdout, stderr, nil
}
func categorizePath(path string) fileScanType {
if strings.HasPrefix(path, *nixStoreRoot) {
if strings.Contains(path, corePkgsString) {
return corePkgsPath
}
return nixStorePath
} else if strings.HasPrefix(path, *depotRoot) {
return depotPath
} else if strings.Contains(path, corePkgsString) {
return corePkgsPath
}
return unknownPath
}
func addPath(path string, out map[fileScanType]map[string]struct{}) {
cat := categorizePath(path)
if out[cat] == nil {
out[cat] = make(map[string]struct{})
}
out[cat][path] = struct{}{}
}
func consumeOutput(stdout, stderr io.ReadCloser) (map[fileScanType]map[string]struct{}, string, error) {
result := make(map[fileScanType]map[string]struct{})
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, depotTraceString) {
addPath(strings.TrimPrefix(line, depotTraceString), result)
} else {
// print remaining stderr output of nix-instantiate
// to prevent silent swallowing of possible important
// error messages (e.g. about command line interface changes)
fmt.Fprintf(os.Stderr, "nix-inst> %s\n", line)
}
}
if scanner.Err() != nil {
return nil, "", scanner.Err()
}
// Get derivation path
derivPath := ""
scanner = bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, *nixStoreRoot) {
derivPath = line
// consume the rest of the output
}
}
if scanner.Err() != nil {
return nil, "", scanner.Err()
}
return result, derivPath, nil
}
func main() {
flag.Parse()
checkDepotRoot()
enabledPathTypes := make(map[pb.PathType]bool, 4)
if len(*onlyFlag) > 0 {
enabledOutputs := strings.Split(*onlyFlag, ",")
for _, v := range enabledOutputs {
i, ok := pb.PathType_value[strings.ToUpper(v)]
if !ok {
fmt.Fprintln(os.Stderr, "warning: unrecognized PathType name: ", v)
continue
}
enabledPathTypes[pb.PathType(i)] = true
}
} else {
// Default
enabledPathTypes = map[pb.PathType]bool{
pb.PathType_UNKNOWN: true,
pb.PathType_DEPOT: true,
pb.PathType_STORE: true,
pb.PathType_CORE: true,
}
}
cmd, stdout, stderr, err := launchNix(flag.Arg(0))
if err != nil {
panic(fmt.Errorf("could not launch nix: %w", err))
}
results, derivPath, err := consumeOutput(stdout, stderr)
if err != nil {
err2 := cmd.Wait()
if err2 != nil {
panic(fmt.Errorf("nix-instantiate failed: %w\nadditionally, while reading output: %w", err2, err))
}
panic(fmt.Errorf("problem reading nix output: %w", err))
}
err = cmd.Wait()
if err != nil {
panic(fmt.Errorf("nix-instantiate failed: %w", err))
}
_ = derivPath
if *modeFlag == "print" {
if enabledPathTypes[pb.PathType_STORE] {
for k, _ := range results[nixStorePath] {
if *relativeFlag {
k = strings.TrimPrefix(k, *nixStoreRoot)
k = strings.TrimPrefix(k, "/")
}
fmt.Println(k)
}
}
if enabledPathTypes[pb.PathType_DEPOT] {
for k, _ := range results[depotPath] {
if *relativeFlag {
k = strings.TrimPrefix(k, *depotRoot)
k = strings.TrimPrefix(k, "/")
}
fmt.Println(k)
}
}
if enabledPathTypes[pb.PathType_CORE] {
for k, _ := range results[corePkgsPath] {
// TODO relativeFlag
fmt.Println(k)
}
}
if enabledPathTypes[pb.PathType_UNKNOWN] {
for k, _ := range results[unknownPath] {
fmt.Println(k)
}
}
} else {
panic("unimplemented")
}
}
func envOr(envVar, def string) string {
v := os.Getenv(envVar)
if v == "" {
return def
}
return v
}
func checkDepotRoot() {
if *depotRoot == "" {
fmt.Fprintln(os.Stderr, "error: DEPOT_ROOT / -depot not set")
os.Exit(2)
}
_, err := os.Stat(*depotRoot)
if os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "error: %q does not exist\ndid you forget to set DEPOT_ROOT / --depot ?\n", *depotRoot)
os.Exit(1)
} else if err != nil {
fmt.Fprintf(os.Stderr, "error: could not stat %q: %v\n", *depotRoot, err)
os.Exit(1)
}
}