2018-05-28 18:28:39 +00:00
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
2020-02-24 12:48:23 +00:00
"strconv"
2018-05-28 18:28:39 +00:00
"strings"
2020-02-11 01:31:54 +00:00
"github.com/go-delve/delve/pkg/goversion"
2018-05-28 18:28:39 +00:00
"github.com/spf13/cobra"
)
2019-01-04 18:39:25 +00:00
const DelveMainPackagePath = "github.com/go-delve/delve/cmd/dlv"
2018-05-28 18:28:39 +00:00
var Verbose bool
2020-01-21 17:11:20 +00:00
var NOTimeout bool
2020-12-16 16:56:15 +00:00
var TestIncludePIE bool
2018-05-29 15:01:51 +00:00
var TestSet , TestRegex , TestBackend , TestBuildMode string
2018-05-28 18:28:39 +00:00
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 ,
} )
RootCommand . AddCommand ( & cobra . Command {
Use : "build" ,
Short : "Build delve" ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2018-10-01 08:19:06 +00:00
tagFlag := prepareMacnative ( )
execute ( "go" , "build" , tagFlag , buildFlags ( ) , DelveMainPackagePath )
if runtime . GOOS == "darwin" && os . Getenv ( "CERT" ) != "" && canMacnative ( ) {
2018-05-28 18:28:39 +00:00
codesign ( "./dlv" )
}
} ,
} )
RootCommand . AddCommand ( & cobra . Command {
Use : "install" ,
Short : "Installs delve" ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2018-10-01 08:19:06 +00:00
tagFlag := prepareMacnative ( )
execute ( "go" , "install" , tagFlag , buildFlags ( ) , DelveMainPackagePath )
if runtime . GOOS == "darwin" && os . Getenv ( "CERT" ) != "" && canMacnative ( ) {
2018-05-28 18:28:39 +00:00
codesign ( installedExecutablePath ( ) )
}
} ,
} )
2019-09-13 03:40:26 +00:00
RootCommand . AddCommand ( & cobra . Command {
Use : "uninstall" ,
Short : "Uninstalls delve" ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
execute ( "go" , "clean" , "-i" , DelveMainPackagePath )
} ,
} )
2018-05-28 18:28:39 +00:00
test := & cobra . Command {
Use : "test" ,
Short : "Tests delve" ,
Long : ` Tests delve .
2020-07-28 16:19:51 +00:00
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 ) .
2018-05-29 15:01:51 +00:00
` ,
2018-05-28 18:28:39 +00:00
Run : testCmd ,
}
2019-11-16 13:43:44 +00:00
test . PersistentFlags ( ) . BoolVarP ( & Verbose , "verbose" , "v" , false , "Verbose tests" )
2020-01-21 17:11:20 +00:00
test . PersistentFlags ( ) . BoolVarP ( & NOTimeout , "timeout" , "t" , false , "Set infinite timeouts" )
2018-05-28 18:28:39 +00:00
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
2019-01-04 18:39:25 +00:00
integration tests github . com / go - delve / delve / service / test
2018-05-28 18:28:39 +00:00
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
2018-05-29 15:01:51 +00:00
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
2018-05-28 18:28:39 +00:00
This option can only be specified if testset is basic or a single package . ` )
2020-12-16 16:56:15 +00:00
test . PersistentFlags ( ) . BoolVarP ( & TestIncludePIE , "pie" , "" , true , "Standard testing should include PIE" )
2018-05-28 18:28:39 +00:00
RootCommand . AddCommand ( test )
RootCommand . AddCommand ( & cobra . Command {
Use : "vendor" ,
Short : "vendors dependencies" ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2019-07-08 22:36:13 +00:00
execute ( "go" , "mod" , "vendor" )
2018-05-28 18:28:39 +00:00
} ,
} )
return RootCommand
}
2018-10-18 15:25:39 +00:00
func checkCert ( ) bool {
2018-05-28 18:28:39 +00:00
// If we're on OSX make sure the proper CERT env var is set.
if os . Getenv ( "TRAVIS" ) == "true" || runtime . GOOS != "darwin" || os . Getenv ( "CERT" ) != "" {
2018-10-18 15:25:39 +00:00
return true
2018-05-28 18:28:39 +00:00
}
2020-03-28 18:18:55 +00:00
x := exec . Command ( "_scripts/gencert.sh" )
2018-10-18 15:25:39 +00:00
x . Stdout = os . Stdout
x . Stderr = os . Stderr
x . Env = os . Environ ( )
2018-05-28 18:28:39 +00:00
err := x . Run ( )
if x . ProcessState != nil && ! x . ProcessState . Success ( ) {
fmt . Printf ( "An error occurred when generating and installing a new certificate\n" )
2018-10-18 15:25:39 +00:00
return false
2018-05-28 18:28:39 +00:00
}
if err != nil {
2018-10-18 15:25:39 +00:00
fmt . Printf ( "An error occoured when generating and installing a new certificate: %v\n" , err )
return false
2018-05-28 18:28:39 +00:00
}
os . Setenv ( "CERT" , "dlv-cert" )
2018-10-18 15:25:39 +00:00
return true
}
func checkCertCmd ( cmd * cobra . Command , args [ ] string ) {
if ! checkCert ( ) {
os . Exit ( 1 )
}
2018-05-28 18:28:39 +00:00
}
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 ( cmd string , args ... interface { } ) {
x := exec . Command ( cmd , strflatten ( args ) ... )
x . Stdout = os . Stdout
x . Stderr = os . Stderr
x . Env = os . Environ ( )
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 ) ) , " " ) )
executeq ( 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 ( )
2019-11-12 14:58:54 +00:00
out , err := x . Output ( )
2018-05-28 18:28:39 +00:00
if err != nil {
2020-03-26 16:25:50 +00:00
fmt . Fprintf ( os . Stderr , "Error executing %s %v\n" , cmd , args )
2018-05-28 18:28:39 +00:00
log . Fatal ( err )
}
if ! x . ProcessState . Success ( ) {
2020-03-26 16:25:50 +00:00
fmt . Fprintf ( os . Stderr , "Error executing %s %v\n" , cmd , args )
2018-05-28 18:28:39 +00:00
os . Exit ( 1 )
}
return string ( out )
}
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" ) , ":" )
2018-09-25 18:26:28 +00:00
return filepath . Join ( strings . TrimSpace ( gopath [ 0 ] ) , "bin" , "dlv" )
2018-05-28 18:28:39 +00:00
}
2018-10-01 08:19:06 +00:00
// 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" {
return false
}
if strings . TrimSpace ( getoutput ( "go" , "env" , "CGO_ENABLED" ) ) != "1" {
return false
}
2020-02-24 12:48:23 +00:00
macOSVersion := strings . Split ( strings . TrimSpace ( getoutput ( "/usr/bin/sw_vers" , "-productVersion" ) ) , "." )
minor , err := strconv . ParseInt ( macOSVersion [ 1 ] , 10 , 64 )
if err != nil {
return false
}
typesHeader := "/usr/include/sys/types.h"
if minor >= 15 {
typesHeader = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/types.h"
}
_ , err = os . Stat ( typesHeader )
2018-10-01 08:19:06 +00:00
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 ""
}
2018-10-18 15:25:39 +00:00
if ! checkCert ( ) {
return ""
}
2018-10-01 08:19:06 +00:00
return "-tags=macnative"
}
2018-05-28 18:28:39 +00:00
func buildFlags ( ) [ ] string {
buildSHA , err := exec . Command ( "git" , "rev-parse" , "HEAD" ) . CombinedOutput ( )
if err != nil {
log . Fatal ( err )
}
ldFlags := "-X main.Build=" + strings . TrimSpace ( string ( buildSHA ) )
if runtime . GOOS == "darwin" {
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" )
}
2020-01-21 17:11:20 +00:00
if NOTimeout {
2020-03-18 22:45:29 +00:00
testFlags = append ( testFlags , "-timeout" , "0" )
2020-04-16 15:40:00 +00:00
} else if os . Getenv ( "TRAVIS" ) == "true" {
// Make test timeout shorter than Travis' own timeout so that Go can report which test hangs.
testFlags = append ( testFlags , "-timeout" , "9m" )
2020-01-21 17:11:20 +00:00
}
2021-01-18 15:48:06 +00:00
if len ( os . Getenv ( "TEAMCITY_VERSION" ) ) > 0 {
testFlags = append ( testFlags , "-json" )
}
2018-05-28 18:28:39 +00:00
if runtime . GOOS == "darwin" {
2020-03-28 18:18:55 +00:00
testFlags = append ( testFlags , "-exec=" + wd + "/_scripts/testsign" )
2018-05-28 18:28:39 +00:00
}
return testFlags
}
func testCmd ( cmd * cobra . Command , args [ ] string ) {
checkCertCmd ( nil , nil )
if os . Getenv ( "TRAVIS" ) == "true" && runtime . GOOS == "darwin" {
2018-10-01 08:19:06 +00:00
fmt . Println ( "Building with native backend" )
execute ( "go" , "build" , "-tags=macnative" , buildFlags ( ) , DelveMainPackagePath )
fmt . Println ( "\nBuilding without native backend" )
execute ( "go" , "build" , buildFlags ( ) , DelveMainPackagePath )
fmt . Println ( "\nTesting" )
2018-05-28 18:28:39 +00:00
os . Setenv ( "PROCTEST" , "lldb" )
executeq ( "sudo" , "-E" , "go" , "test" , testFlags ( ) , allPackages ( ) )
return
}
2018-05-29 15:01:51 +00:00
if TestSet == "" && TestBackend == "" && TestBuildMode == "" {
2018-05-28 18:28:39 +00:00
if TestRegex != "" {
fmt . Printf ( "Can not use --test-run without --test-set\n" )
os . Exit ( 1 )
}
2020-07-28 16:19:51 +00:00
testStandard ( )
2018-05-28 18:28:39 +00:00
return
}
if TestSet == "" {
TestSet = "all"
}
if TestBackend == "" {
TestBackend = "default"
}
2018-05-29 15:01:51 +00:00
if TestBuildMode == "" {
TestBuildMode = "normal"
}
testCmdIntl ( TestSet , TestRegex , TestBackend , TestBuildMode )
2018-05-28 18:28:39 +00:00
}
2020-07-28 16:19:51 +00:00
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" )
}
2020-12-16 16:56:15 +00:00
if TestIncludePIE && ( runtime . GOOS == "linux" || ( runtime . GOOS == "windows" && goversion . VersionAfterOrEqual ( runtime . Version ( ) , 1 , 15 ) ) ) {
2020-07-28 16:19:51 +00:00
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" )
}
}
2018-05-29 15:01:51 +00:00
func testCmdIntl ( testSet , testRegex , testBackend , testBuildMode string ) {
2018-05-28 18:28:39 +00:00
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
}
2018-05-29 15:01:51 +00:00
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
}
2018-05-28 18:28:39 +00:00
if len ( testPackages ) > 3 {
2018-05-29 15:01:51 +00:00
executeq ( "go" , "test" , testFlags ( ) , buildFlags ( ) , testPackages , backendFlag , buildModeFlag )
2018-05-28 18:28:39 +00:00
} else if testRegex != "" {
2018-05-29 15:01:51 +00:00
execute ( "go" , "test" , testFlags ( ) , buildFlags ( ) , testPackages , "-run=" + testRegex , backendFlag , buildModeFlag )
2018-05-28 18:28:39 +00:00
} else {
2018-05-29 15:01:51 +00:00
execute ( "go" , "test" , testFlags ( ) , buildFlags ( ) , testPackages , backendFlag , buildModeFlag )
2018-05-28 18:28:39 +00:00
}
}
func testSetToPackages ( testSet string ) [ ] string {
switch testSet {
case "" , "all" :
return allPackages ( )
case "basic" :
2019-01-04 18:39:25 +00:00
return [ ] string { "github.com/go-delve/delve/pkg/proc" , "github.com/go-delve/delve/service/test" , "github.com/go-delve/delve/pkg/terminal" }
2018-05-28 18:28:39 +00:00
case "integration" :
2019-01-04 18:39:25 +00:00
return [ ] string { "github.com/go-delve/delve/service/test" }
2018-05-28 18:28:39 +00:00
default :
for _ , pkg := range allPackages ( ) {
if pkg == testSet || strings . HasSuffix ( pkg , "/" + testSet ) {
return [ ] string { pkg }
}
}
return nil
}
}
2018-05-29 15:01:51 +00:00
func defaultBackend ( ) string {
if runtime . GOOS == "darwin" {
return "lldb"
}
return "native"
}
2018-05-28 18:28:39 +00:00
func inpath ( exe string ) bool {
path , _ := exec . LookPath ( exe )
return path != ""
}
func allPackages ( ) [ ] string {
r := [ ] string { }
2020-03-26 16:25:50 +00:00
for _ , dir := range strings . Split ( getoutput ( "go" , "list" , "-mod=vendor" , "./..." ) , "\n" ) {
2018-05-28 18:28:39 +00:00
dir = strings . TrimSpace ( dir )
2020-03-28 18:18:55 +00:00
if dir == "" || strings . Contains ( dir , "/vendor/" ) || strings . Contains ( dir , "/_scripts" ) {
2018-05-28 18:28:39 +00:00
continue
}
r = append ( r , dir )
}
sort . Strings ( r )
return r
}
func main ( ) {
2020-03-26 16:25:50 +00:00
allPackages ( ) // checks that vendor directory is synced as a side effect
2018-05-28 18:28:39 +00:00
NewMakeCommands ( ) . Execute ( )
}