gotool.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // Copyright 2021 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package build
  17. import (
  18. "fmt"
  19. "log"
  20. "os"
  21. "os/exec"
  22. "path/filepath"
  23. "runtime"
  24. "strings"
  25. )
  26. type GoToolchain struct {
  27. Root string // GOROOT
  28. // Cross-compilation variables. These are set when running the go tool.
  29. GOARCH string
  30. GOOS string
  31. CC string
  32. }
  33. // Go creates an invocation of the go command.
  34. func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd {
  35. tool := g.goTool(command, args...)
  36. // Configure environment for cross build.
  37. if g.GOARCH != "" && g.GOARCH != runtime.GOARCH {
  38. tool.Env = append(tool.Env, "CGO_ENABLED=1")
  39. tool.Env = append(tool.Env, "GOARCH="+g.GOARCH)
  40. }
  41. if g.GOOS != "" && g.GOOS != runtime.GOOS {
  42. tool.Env = append(tool.Env, "GOOS="+g.GOOS)
  43. }
  44. // Configure C compiler.
  45. if g.CC != "" {
  46. tool.Env = append(tool.Env, "CC="+g.CC)
  47. } else if os.Getenv("CC") != "" {
  48. tool.Env = append(tool.Env, "CC="+os.Getenv("CC"))
  49. }
  50. return tool
  51. }
  52. // Install creates an invocation of 'go install'. The command is configured to output
  53. // executables to the given 'gobin' directory.
  54. //
  55. // This can be used to install auxiliary build tools without modifying the local go.mod and
  56. // go.sum files. To install tools which are not required by go.mod, ensure that all module
  57. // paths in 'args' contain a module version suffix (e.g. "...@latest").
  58. func (g *GoToolchain) Install(gobin string, args ...string) *exec.Cmd {
  59. if !filepath.IsAbs(gobin) {
  60. panic("GOBIN must be an absolute path")
  61. }
  62. tool := g.goTool("install")
  63. tool.Env = append(tool.Env, "GOBIN="+gobin)
  64. tool.Args = append(tool.Args, "-mod=readonly")
  65. tool.Args = append(tool.Args, args...)
  66. // Ensure GOPATH is set because go install seems to absolutely require it. This uses
  67. // 'go env' because it resolves the default value when GOPATH is not set in the
  68. // environment. Ignore errors running go env and leave any complaining about GOPATH to
  69. // the install command.
  70. pathTool := g.goTool("env", "GOPATH")
  71. output, _ := pathTool.Output()
  72. tool.Env = append(tool.Env, "GOPATH="+string(output))
  73. return tool
  74. }
  75. func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd {
  76. if g.Root == "" {
  77. g.Root = runtime.GOROOT()
  78. }
  79. tool := exec.Command(filepath.Join(g.Root, "bin", "go"), command)
  80. tool.Args = append(tool.Args, args...)
  81. tool.Env = append(tool.Env, "GOROOT="+g.Root)
  82. // Forward environment variables to the tool, but skip compiler target settings.
  83. // TODO: what about GOARM?
  84. skip := map[string]struct{}{"GOROOT": {}, "GOARCH": {}, "GOOS": {}, "GOBIN": {}, "CC": {}}
  85. for _, e := range os.Environ() {
  86. if i := strings.IndexByte(e, '='); i >= 0 {
  87. if _, ok := skip[e[:i]]; ok {
  88. continue
  89. }
  90. }
  91. tool.Env = append(tool.Env, e)
  92. }
  93. return tool
  94. }
  95. // DownloadGo downloads the Go binary distribution and unpacks it into a temporary
  96. // directory. It returns the GOROOT of the unpacked toolchain.
  97. func DownloadGo(csdb *ChecksumDB, version string) string {
  98. // Shortcut: if the Go version that runs this script matches the
  99. // requested version exactly, there is no need to download anything.
  100. activeGo := strings.TrimPrefix(runtime.Version(), "go")
  101. if activeGo == version {
  102. log.Printf("-dlgo version matches active Go version %s, skipping download.", activeGo)
  103. return runtime.GOROOT()
  104. }
  105. ucache, err := os.UserCacheDir()
  106. if err != nil {
  107. log.Fatal(err)
  108. }
  109. // For Arm architecture, GOARCH includes ISA version.
  110. os := runtime.GOOS
  111. arch := runtime.GOARCH
  112. if arch == "arm" {
  113. arch = "armv6l"
  114. }
  115. file := fmt.Sprintf("go%s.%s-%s", version, os, arch)
  116. if os == "windows" {
  117. file += ".zip"
  118. } else {
  119. file += ".tar.gz"
  120. }
  121. url := "https://golang.org/dl/" + file
  122. dst := filepath.Join(ucache, file)
  123. if err := csdb.DownloadFile(url, dst); err != nil {
  124. log.Fatal(err)
  125. }
  126. godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", version, os, arch))
  127. if err := ExtractArchive(dst, godir); err != nil {
  128. log.Fatal(err)
  129. }
  130. goroot, err := filepath.Abs(filepath.Join(godir, "go"))
  131. if err != nil {
  132. log.Fatal(err)
  133. }
  134. return goroot
  135. }