Source file src/cmd/go/internal/work/init.go

     1  // Copyright 2017 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  // Build initialization (after flag parsing).
     6  
     7  package work
     8  
     9  import (
    10  	"bytes"
    11  	"cmd/go/internal/base"
    12  	"cmd/go/internal/cfg"
    13  	"cmd/go/internal/fsys"
    14  	"cmd/go/internal/modload"
    15  	"cmd/internal/quoted"
    16  	"fmt"
    17  	"internal/platform"
    18  	"os"
    19  	"os/exec"
    20  	"path/filepath"
    21  	"regexp"
    22  	"runtime"
    23  	"strconv"
    24  	"sync"
    25  )
    26  
    27  var buildInitStarted = false
    28  
    29  func BuildInit() {
    30  	if buildInitStarted {
    31  		base.Fatalf("go: internal error: work.BuildInit called more than once")
    32  	}
    33  	buildInitStarted = true
    34  	base.AtExit(closeBuilders)
    35  
    36  	modload.Init()
    37  	instrumentInit()
    38  	buildModeInit()
    39  	if err := fsys.Init(); err != nil {
    40  		base.Fatal(err)
    41  	}
    42  	if from, replaced := fsys.DirContainsReplacement(cfg.GOMODCACHE); replaced {
    43  		base.Fatalf("go: overlay contains a replacement for %s. Files beneath GOMODCACHE (%s) must not be replaced.", from, cfg.GOMODCACHE)
    44  	}
    45  
    46  	// Make sure -pkgdir is absolute, because we run commands
    47  	// in different directories.
    48  	if cfg.BuildPkgdir != "" && !filepath.IsAbs(cfg.BuildPkgdir) {
    49  		p, err := filepath.Abs(cfg.BuildPkgdir)
    50  		if err != nil {
    51  			fmt.Fprintf(os.Stderr, "go: evaluating -pkgdir: %v\n", err)
    52  			base.SetExitStatus(2)
    53  			base.Exit()
    54  		}
    55  		cfg.BuildPkgdir = p
    56  	}
    57  
    58  	if cfg.BuildP <= 0 {
    59  		base.Fatalf("go: -p must be a positive integer: %v\n", cfg.BuildP)
    60  	}
    61  
    62  	// Make sure CC, CXX, and FC are absolute paths.
    63  	for _, key := range []string{"CC", "CXX", "FC"} {
    64  		value := cfg.Getenv(key)
    65  		args, err := quoted.Split(value)
    66  		if err != nil {
    67  			base.Fatalf("go: %s environment variable could not be parsed: %v", key, err)
    68  		}
    69  		if len(args) == 0 {
    70  			continue
    71  		}
    72  		path := args[0]
    73  		if !filepath.IsAbs(path) && path != filepath.Base(path) {
    74  			base.Fatalf("go: %s environment variable is relative; must be absolute path: %s\n", key, path)
    75  		}
    76  	}
    77  
    78  	// Set covermode if not already set.
    79  	// Ensure that -race and -covermode are compatible.
    80  	if cfg.BuildCoverMode == "" {
    81  		cfg.BuildCoverMode = "set"
    82  		if cfg.BuildRace {
    83  			// Default coverage mode is atomic when -race is set.
    84  			cfg.BuildCoverMode = "atomic"
    85  		}
    86  	}
    87  	if cfg.BuildRace && cfg.BuildCoverMode != "atomic" {
    88  		base.Fatalf(`-covermode must be "atomic", not %q, when -race is enabled`, cfg.BuildCoverMode)
    89  	}
    90  }
    91  
    92  // fuzzInstrumentFlags returns compiler flags that enable fuzzing instrumentation
    93  // on supported platforms.
    94  //
    95  // On unsupported platforms, fuzzInstrumentFlags returns nil, meaning no
    96  // instrumentation is added. 'go test -fuzz' still works without coverage,
    97  // but it generates random inputs without guidance, so it's much less effective.
    98  func fuzzInstrumentFlags() []string {
    99  	if !platform.FuzzInstrumented(cfg.Goos, cfg.Goarch) {
   100  		return nil
   101  	}
   102  	return []string{"-d=libfuzzer"}
   103  }
   104  
   105  func instrumentInit() {
   106  	if !cfg.BuildRace && !cfg.BuildMSan && !cfg.BuildASan {
   107  		return
   108  	}
   109  	if cfg.BuildRace && cfg.BuildMSan {
   110  		fmt.Fprintf(os.Stderr, "go: may not use -race and -msan simultaneously\n")
   111  		base.SetExitStatus(2)
   112  		base.Exit()
   113  	}
   114  	if cfg.BuildRace && cfg.BuildASan {
   115  		fmt.Fprintf(os.Stderr, "go: may not use -race and -asan simultaneously\n")
   116  		base.SetExitStatus(2)
   117  		base.Exit()
   118  	}
   119  	if cfg.BuildMSan && cfg.BuildASan {
   120  		fmt.Fprintf(os.Stderr, "go: may not use -msan and -asan simultaneously\n")
   121  		base.SetExitStatus(2)
   122  		base.Exit()
   123  	}
   124  	if cfg.BuildMSan && !platform.MSanSupported(cfg.Goos, cfg.Goarch) {
   125  		fmt.Fprintf(os.Stderr, "-msan is not supported on %s/%s\n", cfg.Goos, cfg.Goarch)
   126  		base.SetExitStatus(2)
   127  		base.Exit()
   128  	}
   129  	if cfg.BuildRace && !platform.RaceDetectorSupported(cfg.Goos, cfg.Goarch) {
   130  		fmt.Fprintf(os.Stderr, "-race is not supported on %s/%s\n", cfg.Goos, cfg.Goarch)
   131  		base.SetExitStatus(2)
   132  		base.Exit()
   133  	}
   134  	if cfg.BuildASan && !platform.ASanSupported(cfg.Goos, cfg.Goarch) {
   135  		fmt.Fprintf(os.Stderr, "-asan is not supported on %s/%s\n", cfg.Goos, cfg.Goarch)
   136  		base.SetExitStatus(2)
   137  		base.Exit()
   138  	}
   139  	// The current implementation is only compatible with the ASan library from version
   140  	// v7 to v9 (See the description in src/runtime/asan/asan.go). Therefore, using the
   141  	// -asan option must use a compatible version of ASan library, which requires that
   142  	// the gcc version is not less than 7 and the clang version is not less than 9,
   143  	// otherwise a segmentation fault will occur.
   144  	if cfg.BuildASan {
   145  		if err := compilerRequiredAsanVersion(); err != nil {
   146  			fmt.Fprintf(os.Stderr, "%v\n", err)
   147  			base.SetExitStatus(2)
   148  			base.Exit()
   149  		}
   150  	}
   151  
   152  	mode := "race"
   153  	if cfg.BuildMSan {
   154  		mode = "msan"
   155  		// MSAN needs PIE on all platforms except linux/amd64.
   156  		// https://github.com/llvm/llvm-project/blob/llvmorg-13.0.1/clang/lib/Driver/SanitizerArgs.cpp#L621
   157  		if cfg.BuildBuildmode == "default" && (cfg.Goos != "linux" || cfg.Goarch != "amd64") {
   158  			cfg.BuildBuildmode = "pie"
   159  		}
   160  	}
   161  	if cfg.BuildASan {
   162  		mode = "asan"
   163  	}
   164  	modeFlag := "-" + mode
   165  
   166  	// Check that cgo is enabled.
   167  	// Note: On macOS, -race does not require cgo. -asan and -msan still do.
   168  	if !cfg.BuildContext.CgoEnabled && (cfg.Goos != "darwin" || cfg.BuildASan || cfg.BuildMSan) {
   169  		if runtime.GOOS != cfg.Goos || runtime.GOARCH != cfg.Goarch {
   170  			fmt.Fprintf(os.Stderr, "go: %s requires cgo\n", modeFlag)
   171  		} else {
   172  			fmt.Fprintf(os.Stderr, "go: %s requires cgo; enable cgo by setting CGO_ENABLED=1\n", modeFlag)
   173  		}
   174  
   175  		base.SetExitStatus(2)
   176  		base.Exit()
   177  	}
   178  	forcedGcflags = append(forcedGcflags, modeFlag)
   179  	forcedLdflags = append(forcedLdflags, modeFlag)
   180  
   181  	if cfg.BuildContext.InstallSuffix != "" {
   182  		cfg.BuildContext.InstallSuffix += "_"
   183  	}
   184  	cfg.BuildContext.InstallSuffix += mode
   185  	cfg.BuildContext.ToolTags = append(cfg.BuildContext.ToolTags, mode)
   186  }
   187  
   188  func buildModeInit() {
   189  	gccgo := cfg.BuildToolchainName == "gccgo"
   190  	var codegenArg string
   191  
   192  	// Configure the build mode first, then verify that it is supported.
   193  	// That way, if the flag is completely bogus we will prefer to error out with
   194  	// "-buildmode=%s not supported" instead of naming the specific platform.
   195  
   196  	switch cfg.BuildBuildmode {
   197  	case "archive":
   198  		pkgsFilter = pkgsNotMain
   199  	case "c-archive":
   200  		pkgsFilter = oneMainPkg
   201  		if gccgo {
   202  			codegenArg = "-fPIC"
   203  		} else {
   204  			switch cfg.Goos {
   205  			case "darwin", "ios":
   206  				switch cfg.Goarch {
   207  				case "arm64":
   208  					codegenArg = "-shared"
   209  				}
   210  
   211  			case "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris":
   212  				// Use -shared so that the result is
   213  				// suitable for inclusion in a PIE or
   214  				// shared library.
   215  				codegenArg = "-shared"
   216  			}
   217  		}
   218  		cfg.ExeSuffix = ".a"
   219  		ldBuildmode = "c-archive"
   220  	case "c-shared":
   221  		pkgsFilter = oneMainPkg
   222  		if gccgo {
   223  			codegenArg = "-fPIC"
   224  		} else {
   225  			switch cfg.Goos {
   226  			case "linux", "android", "freebsd":
   227  				codegenArg = "-shared"
   228  			case "windows":
   229  				// Do not add usual .exe suffix to the .dll file.
   230  				cfg.ExeSuffix = ""
   231  			}
   232  		}
   233  		ldBuildmode = "c-shared"
   234  	case "default":
   235  		ldBuildmode = "exe"
   236  		if platform.DefaultPIE(cfg.Goos, cfg.Goarch, cfg.BuildRace) {
   237  			ldBuildmode = "pie"
   238  			if cfg.Goos != "windows" && !gccgo {
   239  				codegenArg = "-shared"
   240  			}
   241  		}
   242  	case "exe":
   243  		pkgsFilter = pkgsMain
   244  		ldBuildmode = "exe"
   245  		// Set the pkgsFilter to oneMainPkg if the user passed a specific binary output
   246  		// and is using buildmode=exe for a better error message.
   247  		// See issue #20017.
   248  		if cfg.BuildO != "" {
   249  			pkgsFilter = oneMainPkg
   250  		}
   251  	case "pie":
   252  		if cfg.BuildRace && !platform.DefaultPIE(cfg.Goos, cfg.Goarch, cfg.BuildRace) {
   253  			base.Fatalf("-buildmode=pie not supported when -race is enabled on %s/%s", cfg.Goos, cfg.Goarch)
   254  		}
   255  		if gccgo {
   256  			codegenArg = "-fPIE"
   257  		} else {
   258  			switch cfg.Goos {
   259  			case "aix", "windows":
   260  			default:
   261  				codegenArg = "-shared"
   262  			}
   263  		}
   264  		ldBuildmode = "pie"
   265  	case "shared":
   266  		pkgsFilter = pkgsNotMain
   267  		if gccgo {
   268  			codegenArg = "-fPIC"
   269  		} else {
   270  			codegenArg = "-dynlink"
   271  		}
   272  		if cfg.BuildO != "" {
   273  			base.Fatalf("-buildmode=shared and -o not supported together")
   274  		}
   275  		ldBuildmode = "shared"
   276  	case "plugin":
   277  		pkgsFilter = oneMainPkg
   278  		if gccgo {
   279  			codegenArg = "-fPIC"
   280  		} else {
   281  			codegenArg = "-dynlink"
   282  		}
   283  		cfg.ExeSuffix = ".so"
   284  		ldBuildmode = "plugin"
   285  	default:
   286  		base.Fatalf("buildmode=%s not supported", cfg.BuildBuildmode)
   287  	}
   288  
   289  	if cfg.BuildBuildmode != "default" && !platform.BuildModeSupported(cfg.BuildToolchainName, cfg.BuildBuildmode, cfg.Goos, cfg.Goarch) {
   290  		base.Fatalf("-buildmode=%s not supported on %s/%s\n", cfg.BuildBuildmode, cfg.Goos, cfg.Goarch)
   291  	}
   292  
   293  	if cfg.BuildLinkshared {
   294  		if !platform.BuildModeSupported(cfg.BuildToolchainName, "shared", cfg.Goos, cfg.Goarch) {
   295  			base.Fatalf("-linkshared not supported on %s/%s\n", cfg.Goos, cfg.Goarch)
   296  		}
   297  		if gccgo {
   298  			codegenArg = "-fPIC"
   299  		} else {
   300  			forcedAsmflags = append(forcedAsmflags, "-D=GOBUILDMODE_shared=1",
   301  				"-linkshared")
   302  			codegenArg = "-dynlink"
   303  			forcedGcflags = append(forcedGcflags, "-linkshared")
   304  			// TODO(mwhudson): remove -w when that gets fixed in linker.
   305  			forcedLdflags = append(forcedLdflags, "-linkshared", "-w")
   306  		}
   307  	}
   308  	if codegenArg != "" {
   309  		if gccgo {
   310  			forcedGccgoflags = append([]string{codegenArg}, forcedGccgoflags...)
   311  		} else {
   312  			forcedAsmflags = append([]string{codegenArg}, forcedAsmflags...)
   313  			forcedGcflags = append([]string{codegenArg}, forcedGcflags...)
   314  		}
   315  		// Don't alter InstallSuffix when modifying default codegen args.
   316  		if cfg.BuildBuildmode != "default" || cfg.BuildLinkshared {
   317  			if cfg.BuildContext.InstallSuffix != "" {
   318  				cfg.BuildContext.InstallSuffix += "_"
   319  			}
   320  			cfg.BuildContext.InstallSuffix += codegenArg[1:]
   321  		}
   322  	}
   323  
   324  	switch cfg.BuildMod {
   325  	case "":
   326  		// Behavior will be determined automatically, as if no flag were passed.
   327  	case "readonly", "vendor", "mod":
   328  		if !cfg.ModulesEnabled && !base.InGOFLAGS("-mod") {
   329  			base.Fatalf("build flag -mod=%s only valid when using modules", cfg.BuildMod)
   330  		}
   331  	default:
   332  		base.Fatalf("-mod=%s not supported (can be '', 'mod', 'readonly', or 'vendor')", cfg.BuildMod)
   333  	}
   334  	if !cfg.ModulesEnabled {
   335  		if cfg.ModCacheRW && !base.InGOFLAGS("-modcacherw") {
   336  			base.Fatalf("build flag -modcacherw only valid when using modules")
   337  		}
   338  		if cfg.ModFile != "" && !base.InGOFLAGS("-mod") {
   339  			base.Fatalf("build flag -modfile only valid when using modules")
   340  		}
   341  	}
   342  }
   343  
   344  type version struct {
   345  	name         string
   346  	major, minor int
   347  }
   348  
   349  var compiler struct {
   350  	sync.Once
   351  	version
   352  	err error
   353  }
   354  
   355  // compilerVersion detects the version of $(go env CC).
   356  // It returns a non-nil error if the compiler matches a known version schema but
   357  // the version could not be parsed, or if $(go env CC) could not be determined.
   358  func compilerVersion() (version, error) {
   359  	compiler.Once.Do(func() {
   360  		compiler.err = func() error {
   361  			compiler.name = "unknown"
   362  			cc := os.Getenv("CC")
   363  			cmd := exec.Command(cc, "--version")
   364  			cmd.Env = append(cmd.Environ(), "LANG=C")
   365  			out, err := cmd.Output()
   366  			if err != nil {
   367  				// Compiler does not support "--version" flag: not Clang or GCC.
   368  				return err
   369  			}
   370  
   371  			var match [][]byte
   372  			if bytes.HasPrefix(out, []byte("gcc")) {
   373  				compiler.name = "gcc"
   374  				cmd := exec.Command(cc, "-v")
   375  				cmd.Env = append(cmd.Environ(), "LANG=C")
   376  				out, err := cmd.CombinedOutput()
   377  				if err != nil {
   378  					// gcc, but does not support gcc's "-v" flag?!
   379  					return err
   380  				}
   381  				gccRE := regexp.MustCompile(`gcc version (\d+)\.(\d+)`)
   382  				match = gccRE.FindSubmatch(out)
   383  			} else {
   384  				clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
   385  				if match = clangRE.FindSubmatch(out); len(match) > 0 {
   386  					compiler.name = "clang"
   387  				}
   388  			}
   389  
   390  			if len(match) < 3 {
   391  				return nil // "unknown"
   392  			}
   393  			if compiler.major, err = strconv.Atoi(string(match[1])); err != nil {
   394  				return err
   395  			}
   396  			if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil {
   397  				return err
   398  			}
   399  			return nil
   400  		}()
   401  	})
   402  	return compiler.version, compiler.err
   403  }
   404  
   405  // compilerRequiredAsanVersion is a copy of the function defined in
   406  // cmd/cgo/internal/testsanitizers/cc_test.go
   407  // compilerRequiredAsanVersion reports whether the compiler is the version
   408  // required by Asan.
   409  func compilerRequiredAsanVersion() error {
   410  	compiler, err := compilerVersion()
   411  	if err != nil {
   412  		return fmt.Errorf("-asan: the version of $(go env CC) could not be parsed")
   413  	}
   414  
   415  	switch compiler.name {
   416  	case "gcc":
   417  		if runtime.GOARCH == "ppc64le" && compiler.major < 9 {
   418  			return fmt.Errorf("-asan is not supported with %s compiler %d.%d\n", compiler.name, compiler.major, compiler.minor)
   419  		}
   420  		if compiler.major < 7 {
   421  			return fmt.Errorf("-asan is not supported with %s compiler %d.%d\n", compiler.name, compiler.major, compiler.minor)
   422  		}
   423  	case "clang":
   424  		if compiler.major < 9 {
   425  			return fmt.Errorf("-asan is not supported with %s compiler %d.%d\n", compiler.name, compiler.major, compiler.minor)
   426  		}
   427  	default:
   428  		return fmt.Errorf("-asan: C compiler is not gcc or clang")
   429  	}
   430  	return nil
   431  }
   432  

View as plain text