gopkg.in/constabulary/gb.v0@v0.4.4/cmd/gb/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 11 "github.com/constabulary/gb" 12 "github.com/constabulary/gb/cmd" 13 "github.com/constabulary/gb/cmd/gb/internal/match" 14 "github.com/constabulary/gb/internal/debug" 15 ) 16 17 var ( 18 fs = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 19 cwd string 20 ) 21 22 const ( 23 // disable to keep working directory 24 destroyContext = true 25 ) 26 27 func init() { 28 fs.StringVar(&cwd, "R", cmd.MustGetwd(), "set the project root") // actually the working directory to start the project root search 29 fs.Usage = usage 30 } 31 32 var commands = make(map[string]*cmd.Command) 33 34 // registerCommand registers a command for main. 35 // registerCommand should only be called from init(). 36 func registerCommand(command *cmd.Command) { 37 setCommandDefaults(command) 38 commands[command.Name] = command 39 } 40 41 // atExit functions are called in sequence at the exit of the program. 42 var atExit []func() error 43 44 // exit runs all atExit functions, then calls os.Exit(code). 45 func exit(code int) { 46 for _, fn := range atExit { 47 if err := fn(); err != nil { 48 fmt.Fprintln(os.Stderr, "atExit:", err) 49 } 50 } 51 os.Exit(code) 52 } 53 54 func fatalf(format string, args ...interface{}) { 55 fmt.Fprintf(os.Stderr, "FATAL: "+format+"\n", args...) 56 exit(1) 57 } 58 59 func main() { 60 args := os.Args 61 if len(args) < 2 || args[1] == "-h" { 62 fs.Usage() // usage calles exit(2) 63 } 64 name := args[1] 65 if name == "help" { 66 help(args[2:]) 67 exit(0) 68 } 69 70 command := lookupCommand(name) 71 72 // add extra flags if necessary 73 command.AddFlags(fs) 74 75 // parse 'em 76 err := command.FlagParse(fs, args) 77 if err != nil { 78 fatalf("could not parse flags: %v", err) 79 } 80 81 // reset args to the leftovers from fs.Parse 82 args = fs.Args() 83 84 // if this is the plugin command, ensure the name of the 85 // plugin is first in the list of arguments. 86 if command == commands["plugin"] { 87 args = append([]string{name}, args...) 88 } 89 90 // if cwd was passed in via -R, make sure it is absolute 91 cwd, err := filepath.Abs(cwd) 92 if err != nil { 93 fatalf("could not make project root absolute: %v", err) 94 } 95 96 // construct a project context at the current working directory. 97 ctx, err := newContext(cwd) 98 if err != nil { 99 fatalf("unable to construct context: %v", err) 100 } 101 102 // unless the command wants to handle its own arguments, process 103 // arguments into import paths. 104 if !command.SkipParseArgs { 105 srcdir := filepath.Join(ctx.Projectdir(), "src") 106 for _, a := range args { 107 // support the "all" build alias. This used to be handled 108 // in match.ImportPaths, but that's too messy, so if "all" 109 // is present in the args, replace it with "..." and set cwd 110 // to srcdir. 111 if a == "all" { 112 args = []string{"..."} 113 cwd = srcdir 114 break 115 } 116 } 117 args = match.ImportPaths(srcdir, cwd, args) 118 } 119 120 debug.Debugf("args: %v", args) 121 122 if destroyContext { 123 atExit = append(atExit, ctx.Destroy) 124 } 125 126 if err := command.Run(ctx, args); err != nil { 127 fatalf("command %q failed: %v", name, err) 128 } 129 exit(0) 130 } 131 132 func lookupCommand(name string) *cmd.Command { 133 command, ok := commands[name] 134 if (command != nil && !command.Runnable()) || !ok { 135 plugin, err := lookupPlugin(name) 136 if err != nil { 137 fmt.Fprintf(os.Stderr, "FATAL: unknown command %q\n", name) 138 fs.Usage() // usage calles exit(2) 139 } 140 command = &cmd.Command{ 141 Run: func(ctx *gb.Context, args []string) error { 142 args = append([]string{plugin}, args...) 143 144 env := cmd.MergeEnv(os.Environ(), map[string]string{ 145 "GB_PROJECT_DIR": ctx.Projectdir(), 146 }) 147 148 cmd := exec.Cmd{ 149 Path: plugin, 150 Args: args, 151 Env: env, 152 153 Stdin: os.Stdin, 154 Stdout: os.Stdout, 155 Stderr: os.Stderr, 156 } 157 158 return cmd.Run() 159 }, 160 // plugin should not interpret arguments 161 SkipParseArgs: true, 162 } 163 } 164 setCommandDefaults(command) 165 return command 166 } 167 168 func setCommandDefaults(command *cmd.Command) { 169 170 // add a dummy default AddFlags field if none provided. 171 if command.AddFlags == nil { 172 command.AddFlags = func(*flag.FlagSet) {} 173 } 174 175 // add the default flag parsing if not overrriden. 176 if command.FlagParse == nil { 177 command.FlagParse = func(fs *flag.FlagSet, args []string) error { 178 return fs.Parse(args[2:]) 179 } 180 } 181 } 182 183 func newContext(cwd string) (*gb.Context, error) { 184 return cmd.NewContext( 185 cwd, // project root 186 gb.GcToolchain(), 187 gb.Gcflags(gcflags...), 188 gb.Ldflags(ldflags...), 189 gb.Tags(buildtags...), 190 func(c *gb.Context) error { 191 if !race { 192 return nil 193 } 194 195 // check this is a supported platform 196 if runtime.GOARCH != "amd64" { 197 fatalf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH) 198 } 199 switch runtime.GOOS { 200 case "linux", "windows", "darwin", "freebsd": 201 // supported 202 default: 203 fatalf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH) 204 } 205 206 // check the race runtime is built 207 _, err := os.Stat(filepath.Join(runtime.GOROOT(), "pkg", fmt.Sprintf("%s_%s_race", runtime.GOOS, runtime.GOARCH), "runtime.a")) 208 if os.IsNotExist(err) || err != nil { 209 fatalf("go installation at %s is missing race support. See https://getgb.io/faq/#missing-race-support", runtime.GOROOT()) 210 } 211 212 return gb.WithRace(c) 213 }, 214 ) 215 }