gitlab.com/Raven-IO/raven-delve@v1.22.4/_scripts/make.go (about)

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