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  }