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 }