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