github.com/neilgarb/delve@v1.9.2-nobreaks/_scripts/make.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/go-delve/delve/pkg/goversion"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  const DelveMainPackagePath = "github.com/go-delve/delve/cmd/dlv"
    19  
    20  var Verbose bool
    21  var NOTimeout bool
    22  var TestIncludePIE bool
    23  var TestSet, TestRegex, TestBackend, TestBuildMode string
    24  var Tags *[]string
    25  var Architecture string
    26  var OS string
    27  var DisableGit bool
    28  
    29  func NewMakeCommands() *cobra.Command {
    30  	RootCommand := &cobra.Command{
    31  		Use:   "make.go",
    32  		Short: "make script for delve.",
    33  	}
    34  
    35  	RootCommand.AddCommand(&cobra.Command{
    36  		Use:   "check-cert",
    37  		Short: "Check certificate for macOS.",
    38  		Run:   checkCertCmd,
    39  	})
    40  
    41  	buildCmd := &cobra.Command{
    42  		Use:   "build",
    43  		Short: "Build delve",
    44  		Run: func(cmd *cobra.Command, args []string) {
    45  			tagFlag := prepareMacnative()
    46  			if len(*Tags) > 0 {
    47  				if len(tagFlag) == 0 {
    48  					tagFlag = "-tags="
    49  				} else {
    50  					tagFlag += ","
    51  				}
    52  				tagFlag += strings.Join(*Tags, ",")
    53  			}
    54  			envflags := []string{}
    55  			if len(Architecture) > 0 {
    56  				envflags = append(envflags, "GOARCH="+Architecture)
    57  			}
    58  			if len(OS) > 0 {
    59  				envflags = append(envflags, "GOOS="+OS)
    60  			}
    61  			if len(envflags) > 0 {
    62  				executeEnv(envflags, "go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath)
    63  			} else {
    64  				execute("go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath)
    65  			}
    66  			if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" && canMacnative() {
    67  				codesign("./dlv")
    68  			}
    69  		},
    70  	}
    71  	Tags = buildCmd.PersistentFlags().StringArray("tags", []string{}, "Build tags")
    72  	buildCmd.PersistentFlags().BoolVarP(&DisableGit, "no-git", "G", false, "Do not use git")
    73  	buildCmd.PersistentFlags().StringVar(&Architecture, "GOARCH", "", "Architecture to build for")
    74  	buildCmd.PersistentFlags().StringVar(&OS, "GOOS", "", "OS to build for")
    75  	RootCommand.AddCommand(buildCmd)
    76  
    77  	RootCommand.AddCommand(&cobra.Command{
    78  		Use:   "install",
    79  		Short: "Installs delve",
    80  		Run: func(cmd *cobra.Command, args []string) {
    81  			tagFlag := prepareMacnative()
    82  			execute("go", "install", tagFlag, buildFlags(), DelveMainPackagePath)
    83  			if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" && canMacnative() {
    84  				codesign(installedExecutablePath())
    85  			}
    86  		},
    87  	})
    88  
    89  	RootCommand.AddCommand(&cobra.Command{
    90  		Use:   "uninstall",
    91  		Short: "Uninstalls delve",
    92  		Run: func(cmd *cobra.Command, args []string) {
    93  			execute("go", "clean", "-i", DelveMainPackagePath)
    94  		},
    95  	})
    96  
    97  	test := &cobra.Command{
    98  		Use:   "test",
    99  		Short: "Tests delve",
   100  		Long: `Tests delve.
   101  
   102  Use the flags -s, -r and -b to specify which tests to run. Specifying nothing will run all tests relevant for the current environment (see testStandard).
   103  `,
   104  		Run: testCmd,
   105  	}
   106  	test.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose tests")
   107  	test.PersistentFlags().BoolVarP(&NOTimeout, "timeout", "t", false, "Set infinite timeouts")
   108  	test.PersistentFlags().StringVarP(&TestSet, "test-set", "s", "", `Select the set of tests to run, one of either:
   109  	all		tests all packages
   110  	basic		tests proc, integration and terminal
   111  	integration 	tests github.com/go-delve/delve/service/test
   112  	package-name	test the specified package only
   113  `)
   114  	test.PersistentFlags().StringVarP(&TestRegex, "test-run", "r", "", `Only runs the tests matching the specified regex. This option can only be specified if testset is a single package`)
   115  	test.PersistentFlags().StringVarP(&TestBackend, "test-backend", "b", "", `Runs tests for the specified backend only, one of either:
   116  	default		the default backend
   117  	lldb		lldb backend
   118  	rr		rr backend
   119  
   120  This option can only be specified if testset is basic or a single package.`)
   121  	test.PersistentFlags().StringVarP(&TestBuildMode, "test-build-mode", "m", "", `Runs tests compiling with the specified build mode, one of either:
   122  	normal		normal buildmode (default)
   123  	pie		PIE buildmode
   124  	
   125  This option can only be specified if testset is basic or a single package.`)
   126  	test.PersistentFlags().BoolVarP(&TestIncludePIE, "pie", "", true, "Standard testing should include PIE")
   127  
   128  	RootCommand.AddCommand(test)
   129  
   130  	RootCommand.AddCommand(&cobra.Command{
   131  		Use:   "vendor",
   132  		Short: "vendors dependencies",
   133  		Run: func(cmd *cobra.Command, args []string) {
   134  			execute("go", "mod", "vendor")
   135  		},
   136  	})
   137  
   138  	return RootCommand
   139  }
   140  
   141  func checkCert() bool {
   142  	// If we're on OSX make sure the proper CERT env var is set.
   143  	if os.Getenv("TRAVIS") == "true" || runtime.GOOS != "darwin" || os.Getenv("CERT") != "" {
   144  		return true
   145  	}
   146  
   147  	x := exec.Command("_scripts/gencert.sh")
   148  	x.Stdout = os.Stdout
   149  	x.Stderr = os.Stderr
   150  	x.Env = os.Environ()
   151  	err := x.Run()
   152  	if x.ProcessState != nil && !x.ProcessState.Success() {
   153  		fmt.Printf("An error occurred when generating and installing a new certificate\n")
   154  		return false
   155  	}
   156  	if err != nil {
   157  		fmt.Printf("An error occoured when generating and installing a new certificate: %v\n", err)
   158  		return false
   159  	}
   160  	os.Setenv("CERT", "dlv-cert")
   161  	return true
   162  }
   163  
   164  func checkCertCmd(cmd *cobra.Command, args []string) {
   165  	if !checkCert() {
   166  		os.Exit(1)
   167  	}
   168  }
   169  
   170  func strflatten(v []interface{}) []string {
   171  	r := []string{}
   172  	for _, s := range v {
   173  		switch s := s.(type) {
   174  		case []string:
   175  			r = append(r, s...)
   176  		case string:
   177  			if s != "" {
   178  				r = append(r, s)
   179  			}
   180  		}
   181  	}
   182  	return r
   183  }
   184  
   185  func executeq(env []string, cmd string, args ...interface{}) {
   186  	x := exec.Command(cmd, strflatten(args)...)
   187  	x.Stdout = os.Stdout
   188  	x.Stderr = os.Stderr
   189  	x.Env = os.Environ()
   190  	for _, e := range env {
   191  		x.Env = append(x.Env, e)
   192  	}
   193  	err := x.Run()
   194  	if x.ProcessState != nil && !x.ProcessState.Success() {
   195  		os.Exit(1)
   196  	}
   197  	if err != nil {
   198  		log.Fatal(err)
   199  	}
   200  }
   201  
   202  func execute(cmd string, args ...interface{}) {
   203  	fmt.Printf("%s %s\n", cmd, strings.Join(quotemaybe(strflatten(args)), " "))
   204  	env := []string{}
   205  	executeq(env, cmd, args...)
   206  }
   207  
   208  func executeEnv(env []string, cmd string, args ...interface{}) {
   209  	fmt.Printf("%s %s %s\n", strings.Join(env, " "),
   210  		cmd, strings.Join(quotemaybe(strflatten(args)), " "))
   211  	executeq(env, cmd, args...)
   212  }
   213  
   214  func quotemaybe(args []string) []string {
   215  	for i := range args {
   216  		if strings.Index(args[i], " ") >= 0 {
   217  			args[i] = fmt.Sprintf("%q", args[i])
   218  		}
   219  	}
   220  	return args
   221  }
   222  
   223  func getoutput(cmd string, args ...interface{}) string {
   224  	x := exec.Command(cmd, strflatten(args)...)
   225  	x.Env = os.Environ()
   226  	out, err := x.Output()
   227  	if err != nil {
   228  		fmt.Fprintf(os.Stderr, "Error executing %s %v\n", cmd, args)
   229  		log.Fatal(err)
   230  	}
   231  	if !x.ProcessState.Success() {
   232  		fmt.Fprintf(os.Stderr, "Error executing %s %v\n", cmd, args)
   233  		os.Exit(1)
   234  	}
   235  	return string(out)
   236  }
   237  
   238  func codesign(path string) {
   239  	execute("codesign", "-s", os.Getenv("CERT"), path)
   240  }
   241  
   242  func installedExecutablePath() string {
   243  	if gobin := os.Getenv("GOBIN"); gobin != "" {
   244  		return filepath.Join(gobin, "dlv")
   245  	}
   246  	gopath := strings.Split(getoutput("go", "env", "GOPATH"), ":")
   247  	return filepath.Join(strings.TrimSpace(gopath[0]), "bin", "dlv")
   248  }
   249  
   250  // canMacnative returns true if we can build the native backend for macOS,
   251  // i.e. cgo enabled and the legacy SDK headers:
   252  // https://forums.developer.apple.com/thread/104296
   253  func canMacnative() bool {
   254  	if !(runtime.GOOS == "darwin" && runtime.GOARCH == "amd64") {
   255  		return false
   256  	}
   257  	if strings.TrimSpace(getoutput("go", "env", "CGO_ENABLED")) != "1" {
   258  		return false
   259  	}
   260  
   261  	macOSVersion := strings.Split(strings.TrimSpace(getoutput("/usr/bin/sw_vers", "-productVersion")), ".")
   262  
   263  	major, err := strconv.ParseInt(macOSVersion[0], 10, 64)
   264  	if err != nil {
   265  		return false
   266  	}
   267  	minor, err := strconv.ParseInt(macOSVersion[1], 10, 64)
   268  	if err != nil {
   269  		return false
   270  	}
   271  
   272  	typesHeader := "/usr/include/sys/types.h"
   273  	if major >= 11 || (major == 10 && minor >= 15) {
   274  		sdkpath := strings.TrimSpace(getoutput("xcrun", "--sdk", "macosx", "--show-sdk-path"))
   275  		typesHeader = filepath.Join(sdkpath, "usr", "include", "sys", "types.h")
   276  	}
   277  	_, err = os.Stat(typesHeader)
   278  	if err != nil {
   279  		return false
   280  	}
   281  	return true
   282  }
   283  
   284  // prepareMacnative checks if we can build the native backend for macOS and
   285  // if we can checks the certificate and then returns the -tags flag.
   286  func prepareMacnative() string {
   287  	if !canMacnative() {
   288  		return ""
   289  	}
   290  	if !checkCert() {
   291  		return ""
   292  	}
   293  	return "-tags=macnative"
   294  }
   295  
   296  func buildFlags() []string {
   297  	var ldFlags string
   298  	buildSHA, err := getBuildSHA()
   299  	if err != nil {
   300  		log.Printf("error getting build SHA via git: %w", err)
   301  	} else {
   302  		ldFlags = "-X main.Build=" + buildSHA
   303  	}
   304  	if runtime.GOOS == "darwin" {
   305  		ldFlags = "-s " + ldFlags
   306  	}
   307  	return []string{fmt.Sprintf("-ldflags=%s", ldFlags)}
   308  }
   309  
   310  func testFlags() []string {
   311  	wd, err := os.Getwd()
   312  	if err != nil {
   313  		log.Fatal(err)
   314  	}
   315  	testFlags := []string{"-count", "1", "-p", "1"}
   316  	if Verbose {
   317  		testFlags = append(testFlags, "-v")
   318  	}
   319  	if NOTimeout {
   320  		testFlags = append(testFlags, "-timeout", "0")
   321  	} else if os.Getenv("TRAVIS") == "true" {
   322  		// Make test timeout shorter than Travis' own timeout so that Go can report which test hangs.
   323  		testFlags = append(testFlags, "-timeout", "9m")
   324  	}
   325  	if len(os.Getenv("TEAMCITY_VERSION")) > 0 {
   326  		testFlags = append(testFlags, "-json")
   327  	}
   328  	if runtime.GOOS == "darwin" {
   329  		testFlags = append(testFlags, "-exec="+wd+"/_scripts/testsign")
   330  	}
   331  	return testFlags
   332  }
   333  
   334  func testCmd(cmd *cobra.Command, args []string) {
   335  	checkCertCmd(nil, nil)
   336  
   337  	if os.Getenv("TRAVIS") == "true" && runtime.GOOS == "darwin" {
   338  		fmt.Println("Building with native backend")
   339  		execute("go", "build", "-tags=macnative", buildFlags(), DelveMainPackagePath)
   340  
   341  		fmt.Println("\nBuilding without native backend")
   342  		execute("go", "build", buildFlags(), DelveMainPackagePath)
   343  
   344  		fmt.Println("\nTesting")
   345  		os.Setenv("PROCTEST", "lldb")
   346  		env := []string{}
   347  		executeq(env, "sudo", "-E", "go", "test", testFlags(), allPackages())
   348  		return
   349  	}
   350  
   351  	if TestSet == "" && TestBackend == "" && TestBuildMode == "" {
   352  		if TestRegex != "" {
   353  			fmt.Printf("Can not use --test-run without --test-set\n")
   354  			os.Exit(1)
   355  		}
   356  
   357  		testStandard()
   358  		return
   359  	}
   360  
   361  	if TestSet == "" {
   362  		TestSet = "all"
   363  	}
   364  
   365  	if TestBackend == "" {
   366  		TestBackend = "default"
   367  	}
   368  
   369  	if TestBuildMode == "" {
   370  		TestBuildMode = "normal"
   371  	}
   372  
   373  	testCmdIntl(TestSet, TestRegex, TestBackend, TestBuildMode)
   374  }
   375  
   376  func testStandard() {
   377  	fmt.Println("Testing default backend")
   378  	testCmdIntl("all", "", "default", "normal")
   379  	if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
   380  		fmt.Println("\nTesting LLDB backend")
   381  		testCmdIntl("basic", "", "lldb", "normal")
   382  	}
   383  	if inpath("rr") {
   384  		fmt.Println("\nTesting RR backend")
   385  		testCmdIntl("basic", "", "rr", "normal")
   386  	}
   387  	if TestIncludePIE {
   388  		dopie := false
   389  		switch runtime.GOOS {
   390  		case "linux":
   391  			dopie = true
   392  		case "windows":
   393  			// only on Go 1.15 or later, with CGO_ENABLED and gcc found in path
   394  			if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
   395  				out, err := exec.Command("go", "env", "CGO_ENABLED").CombinedOutput()
   396  				if err != nil {
   397  					panic(err)
   398  				}
   399  				if strings.TrimSpace(string(out)) == "1" {
   400  					if _, err = exec.LookPath("gcc"); err == nil {
   401  						dopie = true
   402  					}
   403  				}
   404  			}
   405  		}
   406  		if dopie {
   407  			fmt.Println("\nTesting PIE buildmode, default backend")
   408  			testCmdIntl("basic", "", "default", "pie")
   409  			testCmdIntl("core", "", "default", "pie")
   410  		}
   411  	}
   412  	if runtime.GOOS == "linux" && inpath("rr") {
   413  		fmt.Println("\nTesting PIE buildmode, RR backend")
   414  		testCmdIntl("basic", "", "rr", "pie")
   415  	}
   416  }
   417  
   418  func testCmdIntl(testSet, testRegex, testBackend, testBuildMode string) {
   419  	testPackages := testSetToPackages(testSet)
   420  	if len(testPackages) == 0 {
   421  		fmt.Printf("Unknown test set %q\n", testSet)
   422  		os.Exit(1)
   423  	}
   424  
   425  	if testRegex != "" && len(testPackages) != 1 {
   426  		fmt.Printf("Can not use test-run with test set %q\n", testSet)
   427  		os.Exit(1)
   428  	}
   429  
   430  	backendFlag := ""
   431  	if testBackend != "" && testBackend != "default" {
   432  		if testSet != "basic" && len(testPackages) != 1 {
   433  			fmt.Printf("Can not use test-backend with test set %q\n", testSet)
   434  			os.Exit(1)
   435  		}
   436  		backendFlag = "-backend=" + testBackend
   437  	}
   438  
   439  	buildModeFlag := ""
   440  	if testBuildMode != "" && testBuildMode != "normal" {
   441  		if testSet != "basic" && len(testPackages) != 1 {
   442  			fmt.Printf("Can not use test-buildmode with test set %q\n", testSet)
   443  			os.Exit(1)
   444  		}
   445  		buildModeFlag = "-test-buildmode=" + testBuildMode
   446  	}
   447  
   448  	if len(testPackages) > 3 {
   449  		env := []string{}
   450  		executeq(env, "go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag)
   451  	} else if testRegex != "" {
   452  		execute("go", "test", testFlags(), buildFlags(), testPackages, "-run="+testRegex, backendFlag, buildModeFlag)
   453  	} else {
   454  		execute("go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag)
   455  	}
   456  }
   457  
   458  func testSetToPackages(testSet string) []string {
   459  	switch testSet {
   460  	case "", "all":
   461  		return allPackages()
   462  
   463  	case "basic":
   464  		return []string{"github.com/go-delve/delve/pkg/proc", "github.com/go-delve/delve/service/test", "github.com/go-delve/delve/pkg/terminal"}
   465  
   466  	case "integration":
   467  		return []string{"github.com/go-delve/delve/service/test"}
   468  
   469  	default:
   470  		for _, pkg := range allPackages() {
   471  			if pkg == testSet || strings.HasSuffix(pkg, "/"+testSet) {
   472  				return []string{pkg}
   473  			}
   474  		}
   475  		return nil
   476  	}
   477  }
   478  
   479  func defaultBackend() string {
   480  	if runtime.GOOS == "darwin" {
   481  		return "lldb"
   482  	}
   483  	return "native"
   484  }
   485  
   486  func inpath(exe string) bool {
   487  	path, _ := exec.LookPath(exe)
   488  	return path != ""
   489  }
   490  
   491  func allPackages() []string {
   492  	r := []string{}
   493  	for _, dir := range strings.Split(getoutput("go", "list", "-mod=vendor", "./..."), "\n") {
   494  		dir = strings.TrimSpace(dir)
   495  		if dir == "" || strings.Contains(dir, "/vendor/") || strings.Contains(dir, "/_scripts") {
   496  			continue
   497  		}
   498  		r = append(r, dir)
   499  	}
   500  	sort.Strings(r)
   501  	return r
   502  }
   503  
   504  // getBuildSHA will invoke git to return the current SHA of the commit at HEAD.
   505  // If invoking git has been disabled, it will return an empty string instead.
   506  func getBuildSHA() (string, error) {
   507  	if DisableGit {
   508  		return "", nil
   509  	}
   510  
   511  	buildSHA, err := exec.Command("git", "rev-parse", "HEAD").CombinedOutput()
   512  	if err != nil {
   513  		return "", err
   514  	}
   515  
   516  	shaStr := strings.TrimSpace(string(buildSHA))
   517  	return shaStr, nil
   518  }
   519  
   520  func main() {
   521  	allPackages() // checks that vendor directory is synced as a side effect
   522  	NewMakeCommands().Execute()
   523  }