You can now provide a list of Nix derivations to tvlc to get a git worktree + sparse-checkout containing only the paths needed to build the specified derivations. Known bugs: even though //third_party is only passed to readdir(), git doesn't know this and includes all of //third_party/*. Change-Id: I9dccebd3fbff4bb04ebd568175cf0a7e37d71ab3 Reviewed-on: https://cl.tvl.fyi/c/depot/+/1826 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in>
		
			
				
	
	
		
			222 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
	
		
			5.2 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)
 | |
| 		}
 | |
| 	}
 | |
| 	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 *relativePath {
 | |
| 					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)
 | |
| 	}
 | |
| 
 | |
| }
 |