github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/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  }