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 }