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 }