github.com/zyedidia/knit@v1.1.2-0.20230901152954-f7d4e39a0e24/cmd/knit/knit.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"runtime/pprof"
    13  
    14  	"github.com/spf13/pflag"
    15  	"github.com/zyedidia/knit"
    16  	"github.com/zyedidia/knit/info"
    17  	"github.com/zyedidia/knit/shell"
    18  )
    19  
    20  func fatal(a ...interface{}) {
    21  	fmt.Fprintln(os.Stderr, a...)
    22  	os.Exit(1)
    23  }
    24  
    25  func optString(flags *pflag.FlagSet, name, short string, val string, user *string, desc string) *string {
    26  	if user != nil {
    27  		return flags.StringP(name, short, *user, desc)
    28  	}
    29  	return flags.StringP(name, short, val, desc)
    30  }
    31  
    32  func optStringSlice(flags *pflag.FlagSet, name, short string, val []string, user *[]string, desc string) *[]string {
    33  	if user != nil {
    34  		return flags.StringSliceP(name, short, *user, desc)
    35  	}
    36  	return flags.StringSliceP(name, short, val, desc)
    37  }
    38  
    39  func optInt(flags *pflag.FlagSet, name, short string, val int, user *int, desc string) *int {
    40  	if user != nil {
    41  		return flags.IntP(name, short, *user, desc)
    42  	}
    43  	return flags.IntP(name, short, val, desc)
    44  }
    45  
    46  func optBool(flags *pflag.FlagSet, name, short string, val bool, user *bool, desc string) *bool {
    47  	if user != nil {
    48  		return flags.BoolP(name, short, *user, desc)
    49  	}
    50  	return flags.BoolP(name, short, val, desc)
    51  }
    52  
    53  func parseFlags(flags *pflag.FlagSet) ([]string, error) {
    54  	var toolargs []string
    55  	args := os.Args[1:]
    56  	for i, a := range args {
    57  		if a == "-t" || a == "--tool" {
    58  			if i == len(args)-1 {
    59  				return nil, fmt.Errorf("flag needs an argument: %s", a)
    60  			}
    61  			toolargs = args[i+2:]
    62  			args = args[:i+2]
    63  			break
    64  		}
    65  	}
    66  	return toolargs, flags.Parse(args)
    67  }
    68  
    69  func main() {
    70  	wd, err := os.Getwd()
    71  	if err != nil {
    72  		fatal(err)
    73  	}
    74  
    75  	user, err := knit.UserDefaults()
    76  	if err != nil {
    77  		fmt.Fprintln(os.Stderr, err)
    78  		os.Exit(1)
    79  	}
    80  
    81  	main := pflag.NewFlagSet("main", pflag.ContinueOnError)
    82  
    83  	knitfile := optString(main, "file", "f", "knitfile", user.Knitfile, "knitfile to use")
    84  	ncpu := optInt(main, "threads", "j", runtime.NumCPU(), user.Ncpu, "number of cores to use")
    85  	dryrun := optBool(main, "dry-run", "n", false, user.DryRun, "print commands without actually executing")
    86  	rundir := optString(main, "directory", "C", "", user.RunDir, "run command from directory")
    87  	always := optBool(main, "always-build", "B", false, user.Always, "unconditionally build all targets")
    88  	quiet := optBool(main, "quiet", "q", false, user.Quiet, "don't print commands")
    89  	style := optString(main, "style", "s", "basic", user.Style, "printer style to use (basic, steps, progress)")
    90  	cache := optString(main, "cache", "", ".", user.CacheDir, "directory for caching internal build information")
    91  	hash := optBool(main, "hash", "", true, user.Hash, "hash files to determine if they are out-of-date")
    92  	updated := optStringSlice(main, "updated", "u", nil, user.Updated, "treat files as updated")
    93  	keep := optBool(main, "keep-going", "", false, user.KeepGoing, "keep going even if recipes fail")
    94  
    95  	path, err := exec.LookPath("sh")
    96  	if err != nil {
    97  		ex, err := os.Executable()
    98  		if err != nil {
    99  			fatal(err)
   100  		}
   101  		path = ex
   102  	}
   103  	shellf := optString(main, "shell", "", path, user.Shell, "shell to use when executing commands")
   104  
   105  	debug := main.BoolP("debug", "D", false, "print debug information")
   106  	tool := main.StringP("tool", "t", "", "subtool to invoke (use '-t list' to list subtools); further flags are passed to the subtool")
   107  	version := main.BoolP("version", "v", false, "show version information")
   108  	cpuprofile := main.String("cpuprofile", "", "write cpu profile to 'file'")
   109  	help := main.BoolP("help", "h", false, "show this help message")
   110  
   111  	// hidden flag for running the internal shell
   112  	shrun := main.StringP("shrun", "c", "", "run shell command using internal shell")
   113  	main.MarkHidden("shrun")
   114  
   115  	toolargs, err := parseFlags(main)
   116  	if err != nil {
   117  		fmt.Fprintln(os.Stderr, err)
   118  		os.Exit(1)
   119  	}
   120  
   121  	if *cpuprofile != "" {
   122  		f, err := os.Create(*cpuprofile)
   123  		if err != nil {
   124  			fatal("could not create CPU profile: ", err)
   125  		}
   126  		defer f.Close()
   127  		if err := pprof.StartCPUProfile(f); err != nil {
   128  			fatal("could not start CPU profile: ", err)
   129  		}
   130  		defer pprof.StopCPUProfile()
   131  	}
   132  
   133  	if *help {
   134  		fmt.Println("Usage of knit:")
   135  		fmt.Println("  knit [TARGETS] [ARGS]")
   136  		fmt.Println()
   137  		fmt.Println("Options:")
   138  		main.PrintDefaults()
   139  		os.Exit(0)
   140  	}
   141  
   142  	if *version {
   143  		fmt.Println("knit version", info.Version)
   144  		os.Exit(0)
   145  	}
   146  
   147  	if *debug {
   148  		log.SetOutput(os.Stdout)
   149  		log.SetFlags(0)
   150  		log.SetPrefix("[debug] ")
   151  	} else {
   152  		log.SetOutput(io.Discard)
   153  	}
   154  
   155  	if *shrun != "" {
   156  		err := shell.Run(*shrun)
   157  		if err != nil {
   158  			fatal(err)
   159  		}
   160  		os.Exit(0)
   161  	}
   162  
   163  	out := os.Stdout
   164  	file, err := knit.Run(out, main.Args(), knit.Flags{
   165  		Knitfile:  *knitfile,
   166  		Ncpu:      *ncpu,
   167  		DryRun:    *dryrun,
   168  		RunDir:    *rundir,
   169  		Always:    *always,
   170  		Quiet:     *quiet,
   171  		Style:     *style,
   172  		CacheDir:  *cache,
   173  		Hash:      *hash,
   174  		Updated:   *updated,
   175  		KeepGoing: *keep,
   176  		Shell:     *shellf,
   177  		Tool:      *tool,
   178  		ToolArgs:  toolargs,
   179  	})
   180  
   181  	rel, rerr := filepath.Rel(file, wd)
   182  	if rerr != nil {
   183  		rel = file
   184  	}
   185  	if file == "" {
   186  		rel = "knit"
   187  	}
   188  
   189  	if errors.Is(err, knit.ErrQuiet) {
   190  		return
   191  	}
   192  	if err != nil {
   193  		fmt.Fprintf(os.Stderr, "%s: %s\n", rel, err)
   194  		if !errors.Is(err, knit.ErrNothingToDo) {
   195  			os.Exit(1)
   196  		}
   197  	}
   198  }