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