main.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "runtime"
  8. "sync"
  9. )
  10. func main() {
  11. // Call realMain so that defers work properly, since os.Exit won't
  12. // call defers.
  13. os.Exit(realMain())
  14. }
  15. func realMain() int {
  16. var buildToolchain bool
  17. var ldflags string
  18. var outputTpl string
  19. var parallel int
  20. var platformFlag PlatformFlag
  21. var tags string
  22. var verbose bool
  23. var flagGcflags string
  24. var flagCgo, flagRebuild, flagListOSArch bool
  25. var flagGoCmd string
  26. flags := flag.NewFlagSet("gox", flag.ExitOnError)
  27. flags.Usage = func() { printUsage() }
  28. flags.Var(platformFlag.ArchFlagValue(), "arch", "arch to build for or skip")
  29. flags.Var(platformFlag.OSArchFlagValue(), "osarch", "os/arch pairs to build for or skip")
  30. flags.Var(platformFlag.OSFlagValue(), "os", "os to build for or skip")
  31. flags.StringVar(&ldflags, "ldflags", "", "linker flags")
  32. flags.StringVar(&tags, "tags", "", "go build tags")
  33. flags.StringVar(&outputTpl, "output", "{{.Dir}}_{{.OS}}_{{.Arch}}", "output path")
  34. flags.IntVar(&parallel, "parallel", -1, "parallelization factor")
  35. flags.BoolVar(&buildToolchain, "build-toolchain", false, "build toolchain")
  36. flags.BoolVar(&verbose, "verbose", false, "verbose")
  37. flags.BoolVar(&flagCgo, "cgo", false, "")
  38. flags.BoolVar(&flagRebuild, "rebuild", false, "")
  39. flags.BoolVar(&flagListOSArch, "osarch-list", false, "")
  40. flags.StringVar(&flagGcflags, "gcflags", "", "")
  41. flags.StringVar(&flagGoCmd, "gocmd", "go", "")
  42. if err := flags.Parse(os.Args[1:]); err != nil {
  43. flags.Usage()
  44. return 1
  45. }
  46. // Determine what amount of parallelism we want Default to the current
  47. // number of CPUs-1 is <= 0 is specified.
  48. if parallel <= 0 {
  49. cpus := runtime.NumCPU()
  50. if cpus < 2 {
  51. parallel = 1
  52. } else {
  53. parallel = cpus - 1
  54. }
  55. // Joyent containers report 48 cores via runtime.NumCPU(), and a
  56. // default of 47 parallel builds causes a panic. Default to 3 on
  57. // Solaris-derived operating systems unless overridden with the
  58. // -parallel flag.
  59. if runtime.GOOS == "solaris" {
  60. parallel = 3
  61. }
  62. }
  63. if buildToolchain {
  64. return mainBuildToolchain(parallel, platformFlag, verbose)
  65. }
  66. if _, err := exec.LookPath(flagGoCmd); err != nil {
  67. fmt.Fprintf(os.Stderr, "%s executable must be on the PATH\n",
  68. flagGoCmd)
  69. return 1
  70. }
  71. version, err := GoVersion()
  72. if err != nil {
  73. fmt.Fprintf(os.Stderr, "error reading Go version: %s", err)
  74. return 1
  75. }
  76. if flagListOSArch {
  77. return mainListOSArch(version)
  78. }
  79. // Determine the packages that we want to compile. Default to the
  80. // current directory if none are specified.
  81. packages := flags.Args()
  82. if len(packages) == 0 {
  83. packages = []string{"."}
  84. }
  85. // Get the packages that are in the given paths
  86. mainDirs, err := GoMainDirs(packages, flagGoCmd)
  87. if err != nil {
  88. fmt.Fprintf(os.Stderr, "Error reading packages: %s", err)
  89. return 1
  90. }
  91. // Determine the platforms we're building for
  92. platforms := platformFlag.Platforms(SupportedPlatforms(version))
  93. if len(platforms) == 0 {
  94. fmt.Println("No valid platforms to build for. If you specified a value")
  95. fmt.Println("for the 'os', 'arch', or 'osarch' flags, make sure you're")
  96. fmt.Println("using a valid value.")
  97. return 1
  98. }
  99. // Build in parallel!
  100. fmt.Printf("Number of parallel builds: %d\n\n", parallel)
  101. var errorLock sync.Mutex
  102. var wg sync.WaitGroup
  103. errors := make([]string, 0)
  104. semaphore := make(chan int, parallel)
  105. for _, platform := range platforms {
  106. for _, path := range mainDirs {
  107. // Start the goroutine that will do the actual build
  108. wg.Add(1)
  109. go func(path string, platform Platform) {
  110. defer wg.Done()
  111. semaphore <- 1
  112. fmt.Printf("--> %15s: %s\n", platform.String(), path)
  113. opts := &CompileOpts{
  114. PackagePath: path,
  115. Platform: platform,
  116. OutputTpl: outputTpl,
  117. Ldflags: ldflags,
  118. Tags: tags,
  119. Cgo: flagCgo,
  120. Rebuild: flagRebuild,
  121. GoCmd: flagGoCmd,
  122. }
  123. // Determine if we have specific CFLAGS or LDFLAGS for this
  124. // GOOS/GOARCH combo and override the defaults if so.
  125. envOverride(&opts.Ldflags, platform, "LDFLAGS")
  126. envOverride(&opts.Gcflags, platform, "GCFLAGS")
  127. if err := GoCrossCompile(opts); err != nil {
  128. errorLock.Lock()
  129. defer errorLock.Unlock()
  130. errors = append(errors,
  131. fmt.Sprintf("%s error: %s", platform.String(), err))
  132. }
  133. <-semaphore
  134. }(path, platform)
  135. }
  136. }
  137. wg.Wait()
  138. if len(errors) > 0 {
  139. fmt.Fprintf(os.Stderr, "\n%d errors occurred:\n", len(errors))
  140. for _, err := range errors {
  141. fmt.Fprintf(os.Stderr, "--> %s\n", err)
  142. }
  143. return 1
  144. }
  145. return 0
  146. }
  147. func printUsage() {
  148. fmt.Fprintf(os.Stderr, helpText)
  149. }
  150. const helpText = `Usage: gox [options] [packages]
  151. Gox cross-compiles Go applications in parallel.
  152. If no specific operating systems or architectures are specified, Gox
  153. will build for all pairs supported by your version of Go.
  154. Options:
  155. -arch="" Space-separated list of architectures to build for
  156. -build-toolchain Build cross-compilation toolchain
  157. -cgo Sets CGO_ENABLED=1, requires proper C toolchain (advanced)
  158. -gcflags="" Additional '-gcflags' value to pass to go build
  159. -ldflags="" Additional '-ldflags' value to pass to go build
  160. -tags="" Additional '-tags' value to pass to go build
  161. -os="" Space-separated list of operating systems to build for
  162. -osarch="" Space-separated list of os/arch pairs to build for
  163. -osarch-list List supported os/arch pairs for your Go version
  164. -output="foo" Output path template. See below for more info
  165. -parallel=-1 Amount of parallelism, defaults to number of CPUs
  166. -gocmd="go" Build command, defaults to Go
  167. -rebuild Force rebuilding of package that were up to date
  168. -verbose Verbose mode
  169. Output path template:
  170. The output path for the compiled binaries is specified with the
  171. "-output" flag. The value is a string that is a Go text template.
  172. The default value is "{{.Dir}}_{{.OS}}_{{.Arch}}". The variables and
  173. their values should be self-explanatory.
  174. Platforms (OS/Arch):
  175. The operating systems and architectures to cross-compile for may be
  176. specified with the "-arch" and "-os" flags. These are space separated lists
  177. of valid GOOS/GOARCH values to build for, respectively. You may prefix an
  178. OS or Arch with "!" to negate and not build for that platform. If the list
  179. is made up of only negations, then the negations will come from the default
  180. list.
  181. Additionally, the "-osarch" flag may be used to specify complete os/arch
  182. pairs that should be built or ignored. The syntax for this is what you would
  183. expect: "darwin/amd64" would be a valid osarch value. Multiple can be space
  184. separated. An os/arch pair can begin with "!" to not build for that platform.
  185. The "-osarch" flag has the highest precedent when determing whether to
  186. build for a platform. If it is included in the "-osarch" list, it will be
  187. built even if the specific os and arch is negated in "-os" and "-arch",
  188. respectively.
  189. Platform Overrides:
  190. The "-gcflags" and "-ldflags" options can be overridden per-platform
  191. by using environment variables. Gox will look for environment variables
  192. in the following format and use those to override values if they exist:
  193. GOX_[OS]_[ARCH]_GCFLAGS
  194. GOX_[OS]_[ARCH]_LDFLAGS
  195. `