Source file src/cmd/go/scriptconds_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main_test
     6  
     7  import (
     8  	"cmd/go/internal/cfg"
     9  	"cmd/internal/script"
    10  	"cmd/internal/script/scripttest"
    11  	"errors"
    12  	"fmt"
    13  	"internal/buildcfg"
    14  	"internal/platform"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"runtime/debug"
    21  	"testing"
    22  
    23  	"golang.org/x/mod/semver"
    24  )
    25  
    26  func scriptConditions(t *testing.T) map[string]script.Cond {
    27  	conds := scripttest.DefaultConds()
    28  
    29  	scripttest.AddToolChainScriptConditions(t, conds, goHostOS, goHostArch)
    30  
    31  	add := func(name string, cond script.Cond) {
    32  		if _, ok := conds[name]; ok {
    33  			panic(fmt.Sprintf("condition %q is already registered", name))
    34  		}
    35  		conds[name] = cond
    36  	}
    37  
    38  	lazyBool := func(summary string, f func() bool) script.Cond {
    39  		return script.OnceCondition(summary, func() (bool, error) { return f(), nil })
    40  	}
    41  
    42  	add("abscc", script.Condition("default $CC path is absolute and exists", defaultCCIsAbsolute))
    43  	add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive))
    44  	add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs))
    45  	add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit))
    46  	add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
    47  	add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
    48  	add("default-cgo", lazyBool("when CGO_ENABLED=1|0 was set in make.bash", defaultCgo))
    49  	add("default-pie", script.Condition("-buildmode=default resolves to -buildmode=pie", defaultPIE))
    50  
    51  	return conds
    52  }
    53  
    54  func defaultCCIsAbsolute(s *script.State) (bool, error) {
    55  	GOOS, _ := s.LookupEnv("GOOS")
    56  	GOARCH, _ := s.LookupEnv("GOARCH")
    57  	defaultCC := cfg.DefaultCC(GOOS, GOARCH)
    58  	if filepath.IsAbs(defaultCC) {
    59  		if _, err := exec.LookPath(defaultCC); err == nil {
    60  			return true, nil
    61  		}
    62  	}
    63  	return false, nil
    64  }
    65  
    66  func ccIs(s *script.State, want string) (bool, error) {
    67  	CC, _ := s.LookupEnv("CC")
    68  	if CC != "" {
    69  		return CC == want, nil
    70  	}
    71  	GOOS, _ := s.LookupEnv("GOOS")
    72  	GOARCH, _ := s.LookupEnv("GOARCH")
    73  	return cfg.DefaultCC(GOOS, GOARCH) == want, nil
    74  }
    75  
    76  func isCaseSensitive() (bool, error) {
    77  	tmpdir, err := os.MkdirTemp(testTmpDir, "case-sensitive")
    78  	if err != nil {
    79  		return false, fmt.Errorf("failed to create directory to determine case-sensitivity: %w", err)
    80  	}
    81  	defer os.RemoveAll(tmpdir)
    82  
    83  	fcap := filepath.Join(tmpdir, "FILE")
    84  	if err := os.WriteFile(fcap, []byte{}, 0644); err != nil {
    85  		return false, fmt.Errorf("error writing file to determine case-sensitivity: %w", err)
    86  	}
    87  
    88  	flow := filepath.Join(tmpdir, "file")
    89  	_, err = os.ReadFile(flow)
    90  	switch {
    91  	case err == nil:
    92  		return false, nil
    93  	case os.IsNotExist(err):
    94  		return true, nil
    95  	default:
    96  		return false, fmt.Errorf("unexpected error reading file when determining case-sensitivity: %w", err)
    97  	}
    98  }
    99  
   100  func isTrimpath() (bool, error) {
   101  	info, _ := debug.ReadBuildInfo()
   102  	if info == nil {
   103  		return false, errors.New("missing build info")
   104  	}
   105  
   106  	for _, s := range info.Settings {
   107  		if s.Key == "-trimpath" && s.Value == "true" {
   108  			return true, nil
   109  		}
   110  	}
   111  	return false, nil
   112  }
   113  
   114  func hasWorkingGit() bool {
   115  	if runtime.GOOS == "plan9" {
   116  		// The Git command is usually not the real Git on Plan 9.
   117  		// See https://golang.org/issues/29640.
   118  		return false
   119  	}
   120  	_, err := exec.LookPath("git")
   121  	return err == nil
   122  }
   123  
   124  // Capture the major, minor and (optionally) patch version, but ignore anything later
   125  var gitVersLineExtract = regexp.MustCompile(`git version\s+(\d+\.\d+(?:\.\d+)?)`)
   126  
   127  func gitVersion() (string, error) {
   128  	gitOut, runErr := exec.Command("git", "version").CombinedOutput()
   129  	if runErr != nil {
   130  		return "v0", fmt.Errorf("failed to execute git version: %w", runErr)
   131  	}
   132  	matches := gitVersLineExtract.FindSubmatch(gitOut)
   133  	if len(matches) < 2 {
   134  		return "v0", fmt.Errorf("git version extraction regexp did not match version line: %q", gitOut)
   135  	}
   136  	return "v" + string(matches[1]), nil
   137  }
   138  
   139  func hasAtLeastGitVersion(minVers string) (bool, error) {
   140  	gitVers, gitVersErr := gitVersion()
   141  	if gitVersErr != nil {
   142  		return false, gitVersErr
   143  	}
   144  	return semver.Compare(minVers, gitVers) <= 0, nil
   145  }
   146  
   147  func gitSupportsSHA256() (bool, error) {
   148  	return hasAtLeastGitVersion("v2.29")
   149  }
   150  
   151  func defaultCgo() bool {
   152  	return buildcfg.DefaultCGO_ENABLED == "1" || buildcfg.DefaultCGO_ENABLED == "0"
   153  }
   154  
   155  // defaultPIE reports whether -buildmode=default resolves to -buildmode=pie for
   156  // the script's GOOS/GOARCH. It assumes -race is not in effect, which is the only
   157  // case where the resolved default buildmode depends on the race flag (PIE is not
   158  // the default with -race on windows). Scripts that build with -race must not rely
   159  // on this condition.
   160  func defaultPIE(s *script.State) (bool, error) {
   161  	GOOS, _ := s.LookupEnv("GOOS")
   162  	GOARCH, _ := s.LookupEnv("GOARCH")
   163  	return platform.DefaultPIE(GOOS, GOARCH, false), nil
   164  }
   165  

View as plain text