Exemplo n.º 1
0
// importPathsNoDotExpansion returns the import paths to use for the given
// command line, but it does no ... expansion.
func importPathsNoDotExpansion(ctx Context, cwd string, args []string) []string {
	srcdir, _ := filepath.Rel(filepath.Join(ctx.Projectdir(), "src"), cwd)
	debug.Debugf("%s %s", cwd, srcdir)
	if srcdir == ".." {
		srcdir = "."
	}
	if len(args) == 0 {
		args = []string{"..."}
	}
	var out []string
	for _, a := range args {
		// Arguments are supposed to be import paths, but
		// as a courtesy to Windows developers, rewrite \ to /
		// in command-line arguments.  Handles .\... and so on.
		if filepath.Separator == '\\' {
			a = strings.Replace(a, `\`, `/`, -1)
		}

		if a == "all" || a == "std" {
			pkgs, err := ctx.AllPackages(a)
			if err != nil {
				fatalf("could not load all packages: %v", err)
			}
			out = append(out, pkgs...)
			continue
		}
		a = path.Join(srcdir, path.Clean(a))
		out = append(out, a)
	}
	return out
}
Exemplo n.º 2
0
func runOut(output io.Writer, dir string, env []string, command string, args ...string) error {
	cmd := exec.Command(command, args...)
	cmd.Dir = dir
	cmd.Stdout = output
	cmd.Stderr = os.Stderr
	cmd.Env = mergeEnvLists(env, envForDir(cmd.Dir))
	debug.Debugf("cd %s; %s", cmd.Dir, cmd.Args)
	err := cmd.Run()
	return err
}
Exemplo n.º 3
0
// BuildPackages produces a tree of *Actions that can be executed to build
// a *Package.
// BuildPackages walks the tree of *Packages and returns a corresponding
// tree of *Actions representing the steps required to build *Package
// and any of its dependencies
func BuildPackages(pkgs ...*Package) (*Action, error) {
	if len(pkgs) < 1 {
		return nil, errors.New("no packages supplied")
	}

	targets := make(map[string]*Action) // maps package importpath to build action

	names := func(pkgs []*Package) []string {
		var names []string
		for _, pkg := range pkgs {
			names = append(names, pkg.ImportPath)
		}
		return names
	}

	// create top level build action to unify all packages
	t0 := time.Now()
	build := Action{
		Name: fmt.Sprintf("build: %s", strings.Join(names(pkgs), ",")),
		Run: func() error {
			debug.Debugf("build duration: %v %v", time.Since(t0), pkgs[0].Statistics.String())
			return nil
		},
	}

	for _, pkg := range pkgs {
		if len(pkg.GoFiles)+len(pkg.CgoFiles) == 0 {
			debug.Debugf("skipping %v: no go files", pkg.ImportPath)
			continue
		}
		a, err := BuildPackage(targets, pkg)
		if err != nil {
			return nil, err
		}
		if a == nil {
			// nothing to do
			continue
		}
		build.Deps = append(build.Deps, a)
	}
	return &build, nil
}
Exemplo n.º 4
0
// TestFlags appends "-test." for flags that are passed to the test binary.
func TestFlags(testArgs []string) []string {
	debug.Debugf("TestFlags: args: %s", testArgs)
	var targs []string
	for _, arg := range testArgs {
		var nArg, nVal, fArg string
		fArg = arg
		if !strings.Contains(arg, "-test.") {
			nArg = strings.TrimPrefix(arg, "-")
			if strings.Contains(nArg, "=") {
				nArgVal := strings.Split(nArg, "=")
				nArg, nVal = nArgVal[0], nArgVal[1]
			}
			if val, ok := testFlagDefn[nArg]; ok {
				// Special handling for -q, needs to be -test.v when passed to the test
				if nArg == "q" {
					nArg = "v"
				}
				if val.passToTest || val.passToAll {
					fArg = "-test." + nArg
					if val.boolVar {
						// boolean variables can be either -bool, or -bool=true
						// some code, see issue 605, expects the latter form, so
						// when present, expand boolean args to their canonical
						// form.
						nVal = "true"
					}
					if nVal != "" {
						fArg = fArg + "=" + nVal
					}
				}
			}
		}
		targs = append(targs, fArg)
	}
	debug.Debugf("testFlags: targs: %s", targs)
	return targs
}
Exemplo n.º 5
0
func matchPackages(c *Context, pattern string) ([]string, error) {
	debug.Debugf("matchPackages: %v", pattern)
	match := func(string) bool { return true }
	treeCanMatch := func(string) bool { return true }
	if pattern != "all" && pattern != "std" {
		match = matchPattern(pattern)
		treeCanMatch = treeCanMatchPattern(pattern)
	}

	var pkgs []string

	src := filepath.Join(c.Projectdir(), "src") + string(filepath.Separator)
	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.IsDir() || path == src {
			return nil
		}

		// Avoid .foo, _foo, and testdata directory trees.
		elem := fi.Name()
		if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
			return filepath.SkipDir
		}

		name := filepath.ToSlash(path[len(src):])
		if pattern == "std" && strings.Contains(name, ".") {
			return filepath.SkipDir
		}
		if !treeCanMatch(name) {
			return filepath.SkipDir
		}
		if !match(name) {
			return nil
		}
		_, err = c.importers[1].Import(name)
		switch err.(type) {
		case nil:
			pkgs = append(pkgs, name)
			return nil
		case *importer.NoGoError:
			return nil // skip
		default:
			return err
		}
	})
	return pkgs, err
}
Exemplo n.º 6
0
// loadTestFuncs returns the testFuncs describing the tests that will be run.
func loadTestFuncs(ptest *importer.Package) (*testFuncs, error) {
	t := &testFuncs{
		Package: ptest,
	}
	debug.Debugf("loadTestFuncs: %v, %v", ptest.TestGoFiles, ptest.XTestGoFiles)
	for _, file := range ptest.TestGoFiles {
		if err := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); err != nil {
			return nil, err
		}
	}
	for _, file := range ptest.XTestGoFiles {
		if err := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
			return nil, err
		}
	}
	return t, nil
}
Exemplo n.º 7
0
// NewContext creates a gb.Context for the project root.
func NewContext(projectroot string, options ...func(*gb.Context) error) (*gb.Context, error) {
	if projectroot == "" {
		return nil, errors.New("project root is blank")
	}

	root, err := FindProjectroot(projectroot)
	if err != nil {
		return nil, errors.Wrap(err, "could not locate project root")
	}
	project := gb.NewProject(root,
		gb.SourceDir(filepath.Join(root, "src")),
		gb.SourceDir(filepath.Join(root, "vendor", "src")),
	)

	debug.Debugf("project root %q", project.Projectdir())
	return project.NewContext(options...)
}
Exemplo n.º 8
0
// RunCommand detects the project root, parses flags and runs the Command.
func RunCommand(fs *flag.FlagSet, cmd *Command, projectroot, goroot string, args []string) error {
	if cmd.AddFlags != nil {
		cmd.AddFlags(fs)
	}
	if err := fs.Parse(args); err != nil {
		fs.Usage()
		os.Exit(1)
	}
	args = fs.Args() // reset to the remaining arguments

	ctx, err := NewContext(projectroot, gb.GcToolchain())
	if err != nil {
		return errors.Wrap(err, "unable to construct context")
	}
	defer ctx.Destroy()

	debug.Debugf("args: %v", args)
	return cmd.Run(ctx, args)
}
Exemplo n.º 9
0
// TestPackages produces a graph of Actions that when executed build
// and test the supplied packages.
func TestPackages(flags []string, pkgs ...*gb.Package) (*gb.Action, error) {
	if len(pkgs) < 1 {
		return nil, errors.New("no test packages provided")
	}
	targets := make(map[string]*gb.Action) // maps package import paths to their test run action

	names := func(pkgs []*gb.Package) []string {
		var names []string
		for _, pkg := range pkgs {
			names = append(names, pkg.ImportPath)
		}
		return names
	}

	// create top level test action to root all test actions
	t0 := time.Now()
	test := gb.Action{
		Name: fmt.Sprintf("test: %s", strings.Join(names(pkgs), ",")),
		Run: func() error {
			debug.Debugf("test duration: %v %v", time.Since(t0), pkgs[0].Statistics.String())
			return nil
		},
	}

	for _, pkg := range pkgs {
		a, err := TestPackage(targets, pkg, flags)
		if err != nil {
			return nil, err
		}
		if a == nil {
			// nothing to do ?? not even a test action ?
			continue
		}
		test.Deps = append(test.Deps, a)
	}
	return &test, nil
}
Exemplo n.º 10
0
func main() {
	args := os.Args
	if len(args) < 2 || args[1] == "-h" {
		fs.Usage() // usage calles exit(2)
	}
	name := args[1]
	if name == "help" {
		help(args[2:])
		exit(0)
	}

	command, ok := commands[name]
	if (command != nil && !command.Runnable()) || !ok {
		plugin, err := lookupPlugin(name)
		if err != nil {
			fmt.Fprintf(os.Stderr, "FATAL: unknown command %q\n", name)
			fs.Usage() // usage calles exit(2)
		}
		command = &cmd.Command{
			Run: func(ctx *gb.Context, args []string) error {
				args = append([]string{plugin}, args...)

				env := cmd.MergeEnv(os.Environ(), map[string]string{
					"GB_PROJECT_DIR": ctx.Projectdir(),
				})

				cmd := exec.Cmd{
					Path: plugin,
					Args: args,
					Env:  env,

					Stdin:  os.Stdin,
					Stdout: os.Stdout,
					Stderr: os.Stderr,
				}

				return cmd.Run()
			},
			// plugin should not interpret arguments
			SkipParseArgs: true,
		}
	}

	// add extra flags if necessary
	if command.AddFlags != nil {
		command.AddFlags(fs)
	}

	var err error
	if command.FlagParse != nil {
		err = command.FlagParse(fs, args)
	} else {
		err = fs.Parse(args[2:])
	}
	if err != nil {
		fatalf("could not parse flags: %v", err)
	}

	args = fs.Args() // reset args to the leftovers from fs.Parse

	debug.Debugf("args: %v", args)

	if command == commands["plugin"] {
		args = append([]string{name}, args...)
	}
	cwd, err := filepath.Abs(cwd) // if cwd was passed in via -R, make sure it is absolute
	if err != nil {
		fatalf("could not make project root absolute: %v", err)
	}

	ctx, err := cmd.NewContext(
		cwd, // project root
		gb.GcToolchain(),
		gb.Gcflags(gcflags...),
		gb.Ldflags(ldflags...),
		gb.Tags(buildtags...),
		func(c *gb.Context) error {
			if !race {
				return nil
			}

			// check this is a supported platform
			if runtime.GOARCH != "amd64" {
				fatalf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
			}
			switch runtime.GOOS {
			case "linux", "windows", "darwin", "freebsd":
				// supported
			default:
				fatalf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
			}

			// check the race runtime is built
			_, err := os.Stat(filepath.Join(runtime.GOROOT(), "pkg", fmt.Sprintf("%s_%s_race", runtime.GOOS, runtime.GOARCH), "runtime.a"))
			if os.IsNotExist(err) || err != nil {
				fatalf("go installation at %s is missing race support. See https://getgb.io/faq/#missing-race-support", runtime.GOROOT())
			}

			return gb.WithRace(c)
		},
	)

	if err != nil {
		fatalf("unable to construct context: %v", err)
	}

	if !command.SkipParseArgs {
		args = importPaths(ctx, cwd, args)
	}

	debug.Debugf("args: %v", args)

	if destroyContext {
		atExit = append(atExit, ctx.Destroy)
	}

	if err := command.Run(ctx, args); err != nil {
		fatalf("command %q failed: %v", name, err)
	}
	exit(0)
}
Exemplo n.º 11
0
func main() {
	fatalf := func(format string, args ...interface{}) {
		fmt.Fprintf(os.Stderr, "FATAL: "+format+"\n", args...)
		os.Exit(1)
	}

	args := os.Args[1:]

	switch {
	case len(args) < 1, args[0] == "-h", args[0] == "-help":
		printUsage(os.Stdout)
		os.Exit(0)
	case args[0] == "help":
		help(args[1:])
		return
	case projectroot == "":
		fatalf("don't run this binary directly, it is meant to be run as 'gb vendor ...'")
	default:
	}

	root, err := cmd.FindProjectroot(projectroot)
	if err != nil {
		fatalf("could not locate project root: %v", err)
	}
	project := gb.NewProject(root,
		gb.SourceDir(filepath.Join(root, "src")),
		gb.SourceDir(filepath.Join(root, "vendor", "src")),
	)

	debug.Debugf("project root %q", project.Projectdir())

	for _, command := range commands {
		if command.Name == args[0] && command.Runnable() {

			// add extra flags if necessary
			if command.AddFlags != nil {
				command.AddFlags(fs)
			}

			if command.FlagParse != nil {
				err = command.FlagParse(fs, args)
			} else {
				err = fs.Parse(args[1:])
			}
			if err != nil {
				fatalf("could not parse flags: %v", err)
			}
			args = fs.Args() // reset args to the leftovers from fs.Parse
			debug.Debugf("args: %v", args)

			ctx, err := project.NewContext(
				gb.GcToolchain(),
			)
			if err != nil {
				fatalf("unable to construct context: %v", err)
			}
			defer ctx.Destroy()

			if err := command.Run(ctx, args); err != nil {
				fatalf("command %q failed: %v", command.Name, err)
			}
			return
		}
	}
	fatalf("unknown command %q ", args[0])
}
Exemplo n.º 12
0
// Destroy removes the temporary working files of this context.
func (c *Context) Destroy() error {
	debug.Debugf("removing work directory: %v", c.workdir)
	return os.RemoveAll(c.workdir)
}
Exemplo n.º 13
0
// isStale returns true if the source pkg is considered to be stale with
// respect to its installed version.
func isStale(pkg *Package) bool {
	switch pkg.ImportPath {
	case "C", "unsafe":
		// synthetic packages are never stale
		return false
	}

	if !pkg.Standard && pkg.Force {
		return true
	}

	// tests are always stale, they are never installed
	if pkg.TestScope {
		return true
	}

	// Package is stale if completely unbuilt.
	var built time.Time
	if fi, err := os.Stat(pkgpath(pkg)); err == nil {
		built = fi.ModTime()
	}

	if built.IsZero() {
		debug.Debugf("%s is missing", pkgpath(pkg))
		return true
	}

	olderThan := func(file string) bool {
		fi, err := os.Stat(file)
		return err != nil || fi.ModTime().After(built)
	}

	newerThan := func(file string) bool {
		fi, err := os.Stat(file)
		return err != nil || fi.ModTime().Before(built)
	}

	// As a courtesy to developers installing new versions of the compiler
	// frequently, define that packages are stale if they are
	// older than the compiler, and commands if they are older than
	// the linker.  This heuristic will not work if the binaries are
	// back-dated, as some binary distributions may do, but it does handle
	// a very common case.
	if !pkg.Standard {
		if olderThan(pkg.tc.compiler()) {
			debug.Debugf("%s is older than %s", pkgpath(pkg), pkg.tc.compiler())
			return true
		}
		if pkg.isMain() && olderThan(pkg.tc.linker()) {
			debug.Debugf("%s is older than %s", pkgpath(pkg), pkg.tc.compiler())
			return true
		}
	}

	if pkg.Standard && !pkg.isCrossCompile() {
		// if this is a standard lib package, and we are not cross compiling
		// then assume the package is up to date. This also works around
		// golang/go#13769.
		return false
	}

	// Package is stale if a dependency is newer.
	for _, p := range pkg.Imports {
		if p.ImportPath == "C" || p.ImportPath == "unsafe" {
			continue // ignore stale imports of synthetic packages
		}
		if olderThan(pkgpath(p)) {
			debug.Debugf("%s is older than %s", pkgpath(pkg), pkgpath(p))
			return true
		}
	}

	// if the main package is up to date but _newer_ than the binary (which
	// could have been removed), then consider it stale.
	if pkg.isMain() && newerThan(pkg.Binfile()) {
		debug.Debugf("%s is newer than %s", pkgpath(pkg), pkg.Binfile())
		return true
	}

	srcs := stringList(pkg.GoFiles, pkg.CFiles, pkg.CXXFiles, pkg.MFiles, pkg.HFiles, pkg.SFiles, pkg.CgoFiles, pkg.SysoFiles, pkg.SwigFiles, pkg.SwigCXXFiles)

	for _, src := range srcs {
		if olderThan(filepath.Join(pkg.Dir, src)) {
			debug.Debugf("%s is older than %s", pkgpath(pkg), filepath.Join(pkg.Dir, src))
			return true
		}
	}

	return false
}
Exemplo n.º 14
0
// TestPackage returns an Action representing the steps required to build
// and test this Package.
func TestPackage(targets map[string]*gb.Action, pkg *gb.Package, flags []string) (*gb.Action, error) {
	debug.Debugf("TestPackage: %s, flags: %s", pkg.ImportPath, flags)
	var gofiles []string
	gofiles = append(gofiles, pkg.GoFiles...)
	gofiles = append(gofiles, pkg.TestGoFiles...)

	var cgofiles []string
	cgofiles = append(cgofiles, pkg.CgoFiles...)

	var imports []string
	imports = append(imports, pkg.Package.Imports...)
	imports = append(imports, pkg.Package.TestImports...)

	name := pkg.Name
	if name == "main" {
		// rename the main package to its package name for testing.
		name = filepath.Base(filepath.FromSlash(pkg.ImportPath))
	}

	// internal tests
	testpkg, err := pkg.NewPackage(&importer.Package{
		Name:       name,
		ImportPath: pkg.ImportPath,
		Dir:        pkg.Dir,
		SrcRoot:    pkg.SrcRoot,

		GoFiles:      gofiles,
		CFiles:       pkg.CFiles,
		CgoFiles:     cgofiles,
		TestGoFiles:  pkg.TestGoFiles,  // passed directly to buildTestMain
		XTestGoFiles: pkg.XTestGoFiles, // passed directly to buildTestMain

		CgoCFLAGS:    pkg.CgoCFLAGS,
		CgoCPPFLAGS:  pkg.CgoCPPFLAGS,
		CgoCXXFLAGS:  pkg.CgoCXXFLAGS,
		CgoLDFLAGS:   pkg.CgoLDFLAGS,
		CgoPkgConfig: pkg.CgoPkgConfig,

		Imports: imports,
	})
	if err != nil {
		return nil, err
	}
	testpkg.TestScope = true
	testpkg.Stale = true // TODO(dfc) NewPackage should get this right

	// only build the internal test if there is Go source or
	// internal test files.
	var testobj *gb.Action
	if len(testpkg.GoFiles)+len(testpkg.CgoFiles)+len(testpkg.TestGoFiles) > 0 {

		// build internal testpkg dependencies
		deps, err := gb.BuildDependencies(targets, testpkg)
		if err != nil {
			return nil, err
		}

		testobj, err = gb.Compile(testpkg, deps...)
		if err != nil {
			return nil, err
		}
	}

	// external tests
	if len(pkg.XTestGoFiles) > 0 {
		xtestpkg, err := pkg.NewPackage(&importer.Package{
			Name:       name,
			ImportPath: pkg.ImportPath + "_test",
			Dir:        pkg.Dir,
			GoFiles:    pkg.XTestGoFiles,
			Imports:    pkg.XTestImports,
		})
		if err != nil {
			return nil, err
		}

		// build external test dependencies
		deps, err := gb.BuildDependencies(targets, xtestpkg)
		if err != nil {
			return nil, err
		}
		xtestpkg.TestScope = true
		xtestpkg.Stale = true
		xtestpkg.ExtraIncludes = filepath.Join(pkg.Workdir(), filepath.FromSlash(pkg.ImportPath), "_test")

		// if there is an internal test object, add it as a dependency.
		if testobj != nil {
			deps = append(deps, testobj)
		}
		testobj, err = gb.Compile(xtestpkg, deps...)
		if err != nil {
			return nil, err
		}
	}

	testmainpkg, err := buildTestMain(testpkg)
	if err != nil {
		return nil, err
	}
	testmain, err := gb.Compile(testmainpkg, testobj)
	if err != nil {
		return nil, err
	}

	return &gb.Action{
		Name: fmt.Sprintf("run: %s", testmainpkg.Binfile()),
		Deps: testmain.Deps,
		Run: func() error {
			// When used with the concurrent executor, building deps and
			// linking the test binary can cause a lot of disk space to be
			// pinned as linking will tend to occur more frequenty than retiring
			// tests.
			//
			// To solve this, we merge the testmain compile step (which includes
			// linking) and the test run and cleanup steps so they are executed
			// as one atomic operation.
			var output bytes.Buffer
			err := testmain.Run() // compile and link
			if err == nil {
				// nope mode means we stop at the compile and link phase.
				if !pkg.Nope {
					cmd := exec.Command(testmainpkg.Binfile(), flags...)
					cmd.Dir = pkg.Dir // tests run in the original source directory
					cmd.Stdout = &output
					cmd.Stderr = &output
					debug.Debugf("%s", cmd.Args)
					err = cmd.Run()                         // run test
					err = errors.Wrapf(err, "%s", cmd.Args) // wrap error if failed
				}

				// test binaries can be very large, so always unlink the
				// binary after the test has run to free up temporary space
				// technically this is done by ctx.Destroy(), but freeing
				// the space earlier is important for projects with many
				// packages
				os.Remove(testmainpkg.Binfile())
			}

			if err != nil {
				fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
			} else {
				fmt.Println(pkg.ImportPath)
			}
			if err != nil || pkg.Verbose {
				io.Copy(os.Stdout, &output)
			}
			return err
		},
	}, nil
}
Exemplo n.º 15
0
		if err != nil {
			return err
		}

		if dotfile != "" {
			f, err := os.Create(dotfile)
			if err != nil {
				return err
			}
			defer f.Close()
			printActions(f, test)
		}

		startSigHandlers()
		return gb.ExecuteConcurrent(test, P, interrupted)
	},
	AddFlags: addTestFlags,
	FlagParse: func(flags *flag.FlagSet, args []string) error {
		var err error
		debug.Debugf("%s", args)
		args, tfs, err = TestFlagsExtraParse(args[2:])
		debug.Debugf("%s %s", args, tfs)
		if err != nil {
			fmt.Fprintf(os.Stderr, "gb test: %s\n", err)
			fmt.Fprintf(os.Stderr, `run "go help test" or "go help testflag" for more information`+"\n")
			exit(2)
		}
		return flags.Parse(args)
	},
}