Newer
Older
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"path/filepath"
)
// Initialization for any invocation.
goarch string
gobin string
gohostarch string
gohostos string
goos string
goarm string
go386 string
gomips string
goroot string
goroot_final string
goextlinkenabled string
gogcflags string // For running built compiler
goldflags string
workdir string
tooldir string
oldgoos string
oldgoarch string
exe string
defaultcc map[string]string
defaultcxx map[string]string
defaultcflags string
defaultldflags string
defaultpkgconfig string
rebuildall bool
defaultclang bool
var okgoarch = []string{
var okgoos = []string{
"solaris",
// find reports the first index of p in l[0:n], or else -1.
func find(p string, l []string) int {
for i, s := range l {
if p == s {
return i
}
}
return -1
// xinit handles initialization of the various global state, like goroot and goarch.
func xinit() {
b := os.Getenv("GOROOT")
if b == "" {
goroot = filepath.Clean(b)
b = os.Getenv("GOROOT_FINAL")
if b == "" {
b = goroot
if b == "" {
b = pathf("%s/bin", goroot)
}
gobin = b
b = os.Getenv("GOOS")
if b == "" {
b = gohostos
}
goos = b
if find(goos, okgoos) < 0 {
fatalf("unknown $GOOS %s", goos)
}
b = os.Getenv("GOARM")
if b == "" {
b = xgetgoarm()
}
goarm = b
b = os.Getenv("GO386")
if b == "" {
if cansse2() {
b = "sse2"
} else {
b = "387"
}
}
go386 = b
b = os.Getenv("GOMIPS")
if b == "" {
b = "hardfloat"
}
gomips = b
b = os.Getenv("GOMIPS64")
if b == "" {
b = "hardfloat"
}
gomips64 = b
if p := pathf("%s/src/all.bash", goroot); !isfile(p) {
fatalf("$GOROOT is not set correctly or not exported\n"+
"\tGOROOT=%s\n"+
"\t%s does not exist", goroot, p)
}
b = os.Getenv("GOHOSTARCH")
if b != "" {
gohostarch = b
}
if find(gohostarch, okgoarch) < 0 {
fatalf("unknown $GOHOSTARCH %s", gohostarch)
}
b = os.Getenv("GOARCH")
if b == "" {
b = gohostarch
}
goarch = b
if find(goarch, okgoarch) < 0 {
fatalf("unknown $GOARCH %s", goarch)
}
b = os.Getenv("GO_EXTLINK_ENABLED")
if b != "" {
if b != "0" && b != "1" {
fatalf("unknown $GO_EXTLINK_ENABLED %s", b)
}
goextlinkenabled = b
}
gogcflags = os.Getenv("BOOT_GO_GCFLAGS")
cc, cxx := "gcc", "g++"
if defaultclang {
cc, cxx = "clang", "clang++"
defaultcc = compilerEnv("CC", cc)
defaultcxx = compilerEnv("CXX", cxx)
defaultcflags = os.Getenv("CFLAGS")
defaultldflags = os.Getenv("LDFLAGS")
b = os.Getenv("PKG_CONFIG")
if b == "" {
b = "pkg-config"
}
defaultpkgconfig = b
// For tools being invoked but also for os.ExpandEnv.
os.Setenv("GO386", go386)
os.Setenv("GOARCH", goarch)
os.Setenv("GOARM", goarm)
os.Setenv("GOHOSTARCH", gohostarch)
os.Setenv("GOHOSTOS", gohostos)
os.Setenv("GOOS", goos)
os.Setenv("GOMIPS", gomips)
os.Setenv("GOMIPS64", gomips64)
os.Setenv("GOROOT", goroot)
os.Setenv("GOROOT_FINAL", goroot_final)
// Use a build cache separate from the default user one.
// Also one that will be wiped out during startup, so that
// make.bash really does start from a clean slate.
// But if the user has specified no caching, don't cache.
if os.Getenv("GOCACHE") != "off" {
os.Setenv("GOCACHE", pathf("%s/pkg/obj/go-build", goroot))
}
os.Setenv("LANG", "C")
os.Setenv("LANGUAGE", "en_US.UTF8")
workdir = xworkdir()
xatexit(rmworkdir)
tooldir = pathf("%s/pkg/tool/%s_%s", goroot, gohostos, gohostarch)
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// compilerEnv returns a map from "goos/goarch" to the
// compiler setting to use for that platform.
// The entry for key "" covers any goos/goarch not explicitly set in the map.
// For example, compilerEnv("CC", "gcc") returns the C compiler settings
// read from $CC, defaulting to gcc.
//
// The result is a map because additional environment variables
// can be set to change the compiler based on goos/goarch settings.
// The following applies to all envNames but CC is assumed to simplify
// the presentation.
//
// If no environment variables are set, we use def for all goos/goarch.
// $CC, if set, applies to all goos/goarch but is overridden by the following.
// $CC_FOR_TARGET, if set, applies to all goos/goarch except gohostos/gohostarch,
// but is overridden by the following.
// If gohostos=goos and gohostarch=goarch, then $CC_FOR_TARGET applies even for gohostos/gohostarch.
// $CC_FOR_goos_goarch, if set, applies only to goos/goarch.
func compilerEnv(envName, def string) map[string]string {
m := map[string]string{"": def}
if env := os.Getenv(envName); env != "" {
m[""] = env
}
if env := os.Getenv(envName + "_FOR_TARGET"); env != "" {
if gohostos != goos || gohostarch != goarch {
m[gohostos+"/"+gohostarch] = m[""]
}
m[""] = env
}
for _, goos := range okgoos {
for _, goarch := range okgoarch {
if env := os.Getenv(envName + "_FOR_" + goos + "_" + goarch); env != "" {
m[goos+"/"+goarch] = env
}
}
}
return m
}
// compilerEnvLookup returns the compiler settings for goos/goarch in map m.
func compilerEnvLookup(m map[string]string, goos, goarch string) string {
if cc := m[goos+"/"+goarch]; cc != "" {
return cc
}
return m[""]
}
func rmworkdir() {
if vflag > 1 {
errprintf("rm -rf %s\n", workdir)
}
xremoveall(workdir)
func chomp(s string) string {
return strings.TrimRight(s, " \t\r\n")
func branchtag(branch string) (tag string, precise bool) {
log := run(goroot, CheckExit, "git", "log", "--decorate=full", "--format=format:%d", "master.."+branch)
tag = branch
for row, line := range strings.Split(log, "\n") {
// Each line is either blank, or looks like
// (tag: refs/tags/go1.4rc2, refs/remotes/origin/release-branch.go1.4, refs/heads/release-branch.go1.4)
// We need to find an element starting with refs/tags/.
const s = " refs/tags/"
i := strings.Index(line, s)
if i < 0 {
continue
}
// Trim off known prefix.
line = line[i+len(s):]
// The tag name ends at a comma or paren.
j := strings.IndexAny(line, ",)")
if j < 0 {
continue // malformed line; ignore it
}
precise = true // tag denotes HEAD
}
break
}
return
// findgoversion determines the Go version to use in the version string.
func findgoversion() string {
// The $GOROOT/VERSION file takes priority, for distributions
path := pathf("%s/VERSION", goroot)
if isfile(path) {
b := chomp(readfile(path))
// Commands such as "dist version > VERSION" will cause
// the shell to create an empty VERSION file and set dist's
// stdout to its fd. dist in turn looks at VERSION and uses
// its content if available, which is empty at this point.
// Only use the VERSION file if it is non-empty.
if b != "" {
// Some builders cross-compile the toolchain on linux-amd64
// and then copy the toolchain to the target builder (say, linux-arm)
// for use there. But on non-release (devel) branches, the compiler
// used on linux-amd64 will be an amd64 binary, and the compiler
// shipped to linux-arm will be an arm binary, so they will have different
// content IDs (they are binaries for different architectures) and so the
// packages compiled by the running-on-amd64 compiler will appear
// stale relative to the running-on-arm compiler. Avoid this by setting
// the version string to something that doesn't begin with devel.
// Then the version string will be used in place of the content ID,
// and the packages will look up-to-date.
// TODO(rsc): Really the builders could be writing out a better VERSION file instead,
// but it is easier to change cmd/dist than to try to make changes to
// the builder while Brad is away.
if strings.HasPrefix(b, "devel") {
if hostType := os.Getenv("META_BUILDLET_HOST_TYPE"); strings.Contains(hostType, "-cross") {
fmt.Fprintf(os.Stderr, "warning: changing VERSION from %q to %q\n", b, "builder "+hostType)
b = "builder " + hostType
}
}
return b
}
}
// The $GOROOT/VERSION.cache file is a cache to avoid invoking
// git every time we run this command. Unlike VERSION, it gets
// deleted by the clean command.
path = pathf("%s/VERSION.cache", goroot)
if isfile(path) {
return chomp(readfile(path))
// Show a nicer error message if this isn't a Git repo.
if !isGitRepo() {
fatalf("FAILED: not a Git repo; must put a VERSION file in $GOROOT")
}
branch := chomp(run(goroot, CheckExit, "git", "rev-parse", "--abbrev-ref", "HEAD"))
// What are the tags along the current branch?
tag := "devel"
precise := false
// If we're on a release branch, use the closest matching tag
// that is on the release branch (and not on the master branch).
if strings.HasPrefix(branch, "release-branch.") {
tag, precise = branchtag(branch)
}
if !precise {
// Tag does not point at HEAD; add hash and date to version.
tag += chomp(run(goroot, CheckExit, "git", "log", "-n", "1", "--format=format: +%h %cd", "HEAD"))
writefile(tag, path, 0)
return tag
// isGitRepo reports whether the working directory is inside a Git repository.
func isGitRepo() bool {
// NB: simply checking the exit code of `git rev-parse --git-dir` would
// suffice here, but that requires deviating from the infrastructure
// provided by `run`.
gitDir := chomp(run(goroot, 0, "git", "rev-parse", "--git-dir"))
if !filepath.IsAbs(gitDir) {
gitDir = filepath.Join(goroot, gitDir)
}
/*
* Initial tree setup.
*/
// The old tools that no longer live in $GOBIN or $GOROOT/bin.
var oldtool = []string{
"5a", "5c", "5g", "5l",
"6a", "6c", "6g", "6l",
"8a", "8c", "8g", "8l",
"9a", "9c", "9g", "9l",
"cgo",
"ebnflint",
"goapi",
"gofix",
"goinstall",
"gomake",
"gopack",
"gopprof",
"gotest",
"gotype",
"govet",
"goyacc",
"quietgcc",
// Unreleased directories (relative to $GOROOT) that should
// not be in release branches.
var unreleased = []string{
"src/debug/goobj",
"src/old",
func setup() {
if p := pathf("%s/bin", goroot); !isdir(p) {
xmkdir(p)
}
if p := pathf("%s/pkg", goroot); !isdir(p) {
xmkdir(p)
}
p := pathf("%s/pkg/%s_%s", goroot, gohostos, gohostarch)
if rebuildall {
xremoveall(p)
}
xmkdirall(p)
if goos != gohostos || goarch != gohostarch {
p := pathf("%s/pkg/%s_%s", goroot, goos, goarch)
if rebuildall {
xremoveall(p)
}
xmkdirall(p)
// We used to use it for C objects.
// Now we use it for the build cache, to separate dist's cache
// from any other cache the user might have.
p = pathf("%s/pkg/obj/go-build", goroot)
if rebuildall {
xremoveall(p)
}
xmkdirall(p)
// Create tool directory.
// We keep it in pkg/, just like the object directory above.
if rebuildall {
xremoveall(tooldir)
}
xmkdirall(tooldir)
// Remove tool binaries from before the tool/gohostos_gohostarch
xremoveall(pathf("%s/bin/tool", goroot))
for _, old := range oldtool {
xremove(pathf("%s/bin/%s", goroot, old))
}
// If $GOBIN is set and has a Go compiler, it must be cleaned.
for _, char := range "56789" {
if isfile(pathf("%s/%c%s", gobin, char, "g")) {
for _, old := range oldtool {
xremove(pathf("%s/%s", gobin, old))
}
break
// For release, make sure excluded things are excluded.
goversion := findgoversion()
if strings.HasPrefix(goversion, "release.") || (strings.HasPrefix(goversion, "go") && !strings.Contains(goversion, "beta")) {
for _, dir := range unreleased {
if p := pathf("%s/%s", goroot, dir); isdir(p) {
fatalf("%s should not exist in release build", p)
*/
// deptab lists changes to the default dependencies for a given prefix.
// deps ending in /* read the whole directory; deps beginning with -
// Note that this table applies only to the build of cmd/go,
// after the main compiler bootstrap.
var deptab = []struct {
prefix string // prefix of target
dep []string // dependency tweaks for targets with that prefix
}{
{"cmd/go/internal/cfg", []string{
"zosarch.go",
{"runtime/internal/sys", []string{
{"go/build", []string{
"zcgo.go",
}},
// depsuffix records the allowed suffixes for source files.
var depsuffix = []string{
// gentab records how to generate some trivial files.
var gentab = []struct {
nameprefix string
gen func(string, string)
}{
{"zdefaultcc.go", mkzdefaultcc},
{"zosarch.go", mkzosarch},
{"zcgo.go", mkzcgo},
// not generated anymore, but delete the file if we see it
{"enam.c", nil},
{"anames5.c", nil},
{"anames6.c", nil},
{"anames8.c", nil},
{"anames9.c", nil},
// installed maps from a dir name (as given to install) to a chan
// closed when the dir's package is installed.
var installed = make(map[string]chan struct{})
func install(dir string) {
<-startInstall(dir)
}
func startInstall(dir string) chan struct{} {
installedMu.Lock()
ch := installed[dir]
if ch == nil {
ch = make(chan struct{})
installed[dir] = ch
go runInstall(dir, ch)
}
installedMu.Unlock()
return ch
}
// runInstall installs the library, package, or binary associated with dir,
// which is relative to $GOROOT/src.
func runInstall(dir string, ch chan struct{}) {
if dir == "net" || dir == "os/user" || dir == "crypto/x509" {
fatalf("go_bootstrap cannot depend on cgo package %s", dir)
defer close(ch)
if dir == "unsafe" {
return
if vflag > 0 {
if goos != gohostos || goarch != gohostarch {
errprintf("%s (%s/%s)\n", dir, goos, goarch)
} else {
errprintf("%s\n", dir)
}
}
workdir := pathf("%s/%s", workdir, dir)
xmkdirall(workdir)
var clean []string
defer func() {
for _, name := range clean {
xremove(name)
}
}()
path := pathf("%s/src/%s", goroot, dir)
name := filepath.Base(dir)
ispkg := !strings.HasPrefix(dir, "cmd/") || strings.Contains(dir, "/internal/")
// Note: code below knows that link.p[targ] is the target.
var (
link []string
targ int
ispackcmd bool
)
ispackcmd = true
link = []string{"pack", pathf("%s/pkg/%s_%s/%s.a", goroot, goos, goarch, dir)}
targ = len(link) - 1
xmkdirall(filepath.Dir(link[targ]))
elem := name
if elem == "go" {
elem = "go_bootstrap"
}
link = []string{pathf("%s/link", tooldir), "-o", pathf("%s/%s%s", tooldir, elem, exe)}
targ = len(link) - 1
ttarg := mtime(link[targ])
// Gather files that are sources for this target.
// Everything in that directory, and any target-specific
// additions.
files := xreaddir(path)
// Remove files beginning with . or _,
// which are likely to be editor temporary files.
// This is the same heuristic build.ScanDir uses.
// There do exist real C files beginning with _,
// so limit that check to just Go files.
files = filter(files, func(p string) bool {
return !strings.HasPrefix(p, ".") && (!strings.HasPrefix(p, "_") || !strings.HasSuffix(p, ".go"))
})
for _, dt := range deptab {
if dir == dt.prefix || strings.HasSuffix(dt.prefix, "/") && strings.HasPrefix(dir, dt.prefix) {
for _, p := range dt.dep {
p = os.ExpandEnv(p)
files = append(files, p)
files = uniq(files)
for i, p := range files {
files[i] = pathf("%s/%s", path, p)
var gofiles, missing []string
stale := rebuildall
files = filter(files, func(p string) bool {
for _, suf := range depsuffix {
if strings.HasSuffix(p, suf) {
goto ok
}
}
return false
t := mtime(p)
if !t.IsZero() && !strings.HasSuffix(p, ".a") && !shouldbuild(p, dir) {
return false
if strings.HasSuffix(p, ".go") {
gofiles = append(gofiles, p)
if t.After(ttarg) {
stale = true
}
if t.IsZero() {
missing = append(missing, p)
}
return true
})
// If there are no files to compile, we're done.
if len(files) == 0 {
return
}
if !stale {
return
}
// For package runtime, copy some files into the work space.
Russ Cox
committed
xmkdirall(pathf("%s/pkg/include", goroot))
// For use by assembly and C files.
Russ Cox
committed
copyfile(pathf("%s/pkg/include/textflag.h", goroot),
pathf("%s/src/runtime/textflag.h", goroot), 0)
Russ Cox
committed
copyfile(pathf("%s/pkg/include/funcdata.h", goroot),
pathf("%s/src/runtime/funcdata.h", goroot), 0)
Michael Hudson-Doyle
committed
copyfile(pathf("%s/pkg/include/asm_ppc64x.h", goroot),
pathf("%s/src/runtime/asm_ppc64x.h", goroot), 0)
}
// Generate any missing files; regenerate existing ones.
for _, p := range files {
elem := filepath.Base(p)
for _, gt := range gentab {
if gt.gen == nil {
continue
}
if strings.HasPrefix(elem, gt.nameprefix) {
if vflag > 1 {
errprintf("generate %s\n", p)
}
gt.gen(path, p)
// Do not add generated file to clean list.
// In runtime, we want to be able to
// build the package with the go tool,
// and it assumes these generated files already
// exist (it does not know how to build them).
// The 'clean' command can remove
// the generated files.
goto built
if find(p, missing) >= 0 {
}
built:
// Make sure dependencies are installed.
var deps []string
for _, p := range gofiles {
deps = append(deps, readimports(p)...)
}
for _, dir1 := range deps {
startInstall(dir1)
}
for _, dir1 := range deps {
install(dir1)
}
if goos != gohostos || goarch != gohostarch {
// We've generated the right files; the go command can do the build.
if vflag > 1 {
errprintf("skip build for cross-compile %s\n", dir)
}
return
var archive string
// The next loop will compile individual non-Go files.
// Hand the Go files to the compiler en masse.
// For package runtime, this writes go_asm.h, which
// the assembly files will need.
pkg := dir
if strings.HasPrefix(dir, "cmd/") && strings.Count(dir, "/") == 1 {
pkg = "main"
}
b := pathf("%s/_go_.a", workdir)
clean = append(clean, b)
if !ispackcmd {
link = append(link, b)
} else {
archive = b
compile := []string{pathf("%s/compile", tooldir), "-std", "-pack", "-o", b, "-p", pkg}
if gogcflags != "" {
compile = append(compile, strings.Fields(gogcflags)...)
if dir == "runtime" {
compile = append(compile, "-+", "-asmhdr", pathf("%s/go_asm.h", workdir))
}
if dir == "internal/bytealg" {
// TODO: why don't we generate go_asm.h for all packages
// that have any assembly?
compile = append(compile, "-asmhdr", pathf("%s/go_asm.h", workdir))
}
compile = append(compile, gofiles...)
run(path, CheckExit|ShowOutput, compile...)
var wg sync.WaitGroup
for _, p := range files {
if !strings.HasSuffix(p, ".s") {
continue
}
var compile []string
// Assembly file for a Go package.
compile = []string{
Russ Cox
committed
"-I", pathf("%s/pkg/include", goroot),
"-D", "GOOS_" + goos,
"-D", "GOARCH_" + goarch,
"-D", "GOOS_GOARCH_" + goos + "_" + goarch,
if goarch == "mips" || goarch == "mipsle" {
// Define GOMIPS_value from gomips.
compile = append(compile, "-D", "GOMIPS_"+gomips)
}
if goarch == "mips64" || goarch == "mipsle64" {
// Define GOMIPS64_value from gomips64.
compile = append(compile, "-D", "GOMIPS64_"+gomips64)
}
doclean := true
b := pathf("%s/%s", workdir, filepath.Base(p))
// Change the last character of the output file (which was c or s).
b = b[:len(b)-1] + "o"
compile = append(compile, "-o", b, p)
bgrun(&wg, path, compile...)
link = append(link, b)
if doclean {
clean = append(clean, b)
}
xremove(link[targ])
dopack(link[targ], archive, link[targ+1:])
return
xremove(link[targ])
run("", CheckExit|ShowOutput, link...)
// matchfield reports whether the field (x,y,z) matches this build.
// all the elements in the field must be satisfied.
func matchfield(f string) bool {
for _, tag := range strings.Split(f, ",") {
}
}
return true
// matchtag reports whether the tag (x or !x) matches this build.
func matchtag(tag string) bool {
if tag == "" {
return false
}
if tag[0] == '!' {
if len(tag) == 1 || tag[1] == '!' {
return false
}
return !matchtag(tag[1:])
}
return tag == "gc" || tag == goos || tag == goarch || tag == "cmd_go_bootstrap" || tag == "go1.1" || (goos == "android" && tag == "linux")
// shouldbuild reports whether we should build this file.
// It applies the same rules that are used with context tags
// in package go/build, except it's less picky about the order
// of GOOS and GOARCH.
// We also allow the special tag cmd_go_bootstrap.
// See ../go/bootstrap.go and package go/build.
func shouldbuild(file, dir string) bool {
name := filepath.Base(file)
excluded := func(list []string, ok string) bool {
for _, x := range list {
if x == ok || ok == "android" && x == "linux" {
continue
}
i := strings.Index(name, x)
if i <= 0 || name[i-1] != '_' {
continue
}
i += len(x)
if i == len(name) || name[i] == '.' || name[i] == '_' {
return true
}
}
return false
}
if excluded(okgoos, goos) || excluded(okgoarch, goarch) {
return false
if strings.Contains(name, "_test") {
return false
}
for _, p := range strings.Split(readfile(file), "\n") {
p = strings.TrimSpace(p)
if p == "" {
continue
}
code := p
i := strings.Index(code, "//")
if i > 0 {
code = strings.TrimSpace(code[:i])
}
if code == "package documentation" {
return false
if code == "package main" && dir != "cmd/go" && dir != "cmd/cgo" {
return false
if !strings.HasPrefix(p, "//") {
break
if !strings.Contains(p, "+build") {
continue
}
fields := strings.Fields(p[2:])
if len(fields) < 1 || fields[0] != "+build" {
continue
}
for _, p := range fields[1:] {
goto fieldmatch
}
}
return false
fieldmatch:
return true
}
// copy copies the file src to dst, via memory (so only good for small files).
func copyfile(dst, src string, flag int) {
if vflag > 1 {
errprintf("cp %s %s\n", src, dst)
}
writefile(readfile(src), dst, flag)
// dopack copies the package src to dst,
// appending the files listed in extra.
// The archive format is the traditional Unix ar format.
func dopack(dst, src string, extra []string) {
bdst := bytes.NewBufferString(readfile(src))
for _, file := range extra {
b := readfile(file)
// find last path element for archive member name
i := strings.LastIndex(file, "/") + 1
j := strings.LastIndex(file, `\`) + 1
if i < j {
i = j
fmt.Fprintf(bdst, "%-16.16s%-12d%-6d%-6d%-8o%-10d`\n", file[i:], 0, 0, 0, 0644, len(b))
bdst.WriteString(b)
if len(b)&1 != 0 {
bdst.WriteByte(0)
writefile(bdst.String(), dst, 0)
var runtimegen = []string{
"zaexperiment.h",
"zversion.go",