summaryrefslogtreecommitdiff
path: root/src/cmd/dist/build.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/dist/build.c')
-rw-r--r--src/cmd/dist/build.c1520
1 files changed, 1520 insertions, 0 deletions
diff --git a/src/cmd/dist/build.c b/src/cmd/dist/build.c
new file mode 100644
index 000000000..1b68883ce
--- /dev/null
+++ b/src/cmd/dist/build.c
@@ -0,0 +1,1520 @@
+// 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.
+
+#include "a.h"
+#include "arg.h"
+
+/*
+ * Initialization for any invocation.
+ */
+
+// The usual variables.
+char *goarch;
+char *gobin;
+char *gohostarch;
+char *gohostchar;
+char *gohostos;
+char *goos;
+char *goroot = GOROOT_FINAL;
+char *goroot_final = GOROOT_FINAL;
+char *workdir;
+char *tooldir;
+char *gochar;
+char *goversion;
+char *slash; // / for unix, \ for windows
+
+bool rebuildall = 0;
+
+static bool shouldbuild(char*, char*);
+static void copy(char*, char*, int);
+static char *findgoversion(void);
+
+// The known architecture letters.
+static char *gochars = "568";
+
+// The known architectures.
+static char *okgoarch[] = {
+ // same order as gochars
+ "arm",
+ "amd64",
+ "386",
+};
+
+// The known operating systems.
+static char *okgoos[] = {
+ "darwin",
+ "linux",
+ "freebsd",
+ "netbsd",
+ "openbsd",
+ "plan9",
+ "windows",
+};
+
+static void rmworkdir(void);
+
+// find reports the first index of p in l[0:n], or else -1.
+int
+find(char *p, char **l, int n)
+{
+ int i;
+
+ for(i=0; i<n; i++)
+ if(streq(p, l[i]))
+ return i;
+ return -1;
+}
+
+// init handles initialization of the various global state, like goroot and goarch.
+void
+init(void)
+{
+ char *p;
+ int i;
+ Buf b;
+
+ binit(&b);
+
+ xgetenv(&b, "GOROOT");
+ if(b.len > 0) {
+ // if not "/", then strip trailing path separator
+ if(b.len >= 2 && b.p[b.len - 1] == slash[0])
+ b.len--;
+ goroot = btake(&b);
+ }
+
+ xgetenv(&b, "GOBIN");
+ if(b.len == 0)
+ bprintf(&b, "%s%sbin", goroot, slash);
+ gobin = btake(&b);
+
+ xgetenv(&b, "GOOS");
+ if(b.len == 0)
+ bwritestr(&b, gohostos);
+ goos = btake(&b);
+ if(find(goos, okgoos, nelem(okgoos)) < 0)
+ fatal("unknown $GOOS %s", goos);
+
+ p = bpathf(&b, "%s/include/u.h", goroot);
+ if(!isfile(p)) {
+ fatal("$GOROOT is not set correctly or not exported\n"
+ "\tGOROOT=%s\n"
+ "\t%s does not exist", goroot, p);
+ }
+
+ xgetenv(&b, "GOHOSTARCH");
+ if(b.len > 0)
+ gohostarch = btake(&b);
+
+ i = find(gohostarch, okgoarch, nelem(okgoarch));
+ if(i < 0)
+ fatal("unknown $GOHOSTARCH %s", gohostarch);
+ bprintf(&b, "%c", gochars[i]);
+ gohostchar = btake(&b);
+
+ xgetenv(&b, "GOARCH");
+ if(b.len == 0)
+ bwritestr(&b, gohostarch);
+ goarch = btake(&b);
+ i = find(goarch, okgoarch, nelem(okgoarch));
+ if(i < 0)
+ fatal("unknown $GOARCH %s", goarch);
+ bprintf(&b, "%c", gochars[i]);
+ gochar = btake(&b);
+
+ xsetenv("GOROOT", goroot);
+ xsetenv("GOARCH", goarch);
+ xsetenv("GOOS", goos);
+
+ // Make the environment more predictable.
+ xsetenv("LANG", "C");
+ xsetenv("LANGUAGE", "en_US.UTF8");
+
+ goversion = findgoversion();
+
+ workdir = xworkdir();
+ xatexit(rmworkdir);
+
+ bpathf(&b, "%s/pkg/tool/%s_%s", goroot, gohostos, gohostarch);
+ tooldir = btake(&b);
+
+ bfree(&b);
+}
+
+// rmworkdir deletes the work directory.
+static void
+rmworkdir(void)
+{
+ if(vflag > 1)
+ xprintf("rm -rf %s\n", workdir);
+ xremoveall(workdir);
+}
+
+// Remove trailing spaces.
+static void
+chomp(Buf *b)
+{
+ int c;
+
+ while(b->len > 0 && ((c=b->p[b->len-1]) == ' ' || c == '\t' || c == '\r' || c == '\n'))
+ b->len--;
+}
+
+
+// findgoversion determines the Go version to use in the version string.
+static char*
+findgoversion(void)
+{
+ char *tag, *rev, *p;
+ int i, nrev;
+ Buf b, path, bmore, branch;
+ Vec tags;
+
+ binit(&b);
+ binit(&path);
+ binit(&bmore);
+ binit(&branch);
+ vinit(&tags);
+
+ // The $GOROOT/VERSION file takes priority, for distributions
+ // without the Mercurial repo.
+ bpathf(&path, "%s/VERSION", goroot);
+ if(isfile(bstr(&path))) {
+ readfile(&b, bstr(&path));
+ chomp(&b);
+ // 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.
+ if(b.len > 0)
+ goto done;
+ }
+
+ // The $GOROOT/VERSION.cache file is a cache to avoid invoking
+ // hg every time we run this command. Unlike VERSION, it gets
+ // deleted by the clean command.
+ bpathf(&path, "%s/VERSION.cache", goroot);
+ if(isfile(bstr(&path))) {
+ readfile(&b, bstr(&path));
+ chomp(&b);
+ goto done;
+ }
+
+ // Otherwise, use Mercurial.
+ // What is the current branch?
+ run(&branch, goroot, CheckExit, "hg", "identify", "-b", nil);
+ chomp(&branch);
+
+ // What are the tags along the current branch?
+ tag = "";
+ rev = ".";
+ run(&b, goroot, CheckExit, "hg", "log", "-b", bstr(&branch), "--template", "{tags} + ", nil);
+ splitfields(&tags, bstr(&b));
+ nrev = 0;
+ for(i=0; i<tags.len; i++) {
+ p = tags.p[i];
+ if(streq(p, "+"))
+ nrev++;
+ if(hasprefix(p, "release.") || hasprefix(p, "weekly.") || hasprefix(p, "go")) {
+ tag = xstrdup(p);
+ // If this tag matches the current checkout
+ // exactly (no "+" yet), don't show extra
+ // revision information.
+ if(nrev == 0)
+ rev = "";
+ break;
+ }
+ }
+
+ if(tag[0] == '\0') {
+ // Did not find a tag; use branch name.
+ bprintf(&b, "branch.%s", bstr(&branch));
+ tag = btake(&b);
+ }
+
+ if(rev[0]) {
+ // Tag is before the revision we're building.
+ // Add extra information.
+ run(&bmore, goroot, CheckExit, "hg", "log", "--template", " +{node|short}", "-r", rev, nil);
+ chomp(&bmore);
+ }
+
+ bprintf(&b, "%s", tag);
+ if(bmore.len > 0)
+ bwriteb(&b, &bmore);
+
+ // Cache version.
+ writefile(&b, bstr(&path), 0);
+
+done:
+ p = btake(&b);
+
+
+ bfree(&b);
+ bfree(&path);
+ bfree(&bmore);
+ bfree(&branch);
+ vfree(&tags);
+
+ return p;
+}
+
+/*
+ * Initial tree setup.
+ */
+
+// The old tools that no longer live in $GOBIN or $GOROOT/bin.
+static char *oldtool[] = {
+ "5a", "5c", "5g", "5l",
+ "6a", "6c", "6g", "6l",
+ "8a", "8c", "8g", "8l",
+ "6cov",
+ "6nm",
+ "6prof",
+ "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.
+static char *unreleased[] = {
+ "src/cmd/cov",
+ "src/cmd/prof",
+ "src/pkg/old",
+ "src/pkg/exp",
+};
+
+// setup sets up the tree for the initial build.
+static void
+setup(void)
+{
+ int i;
+ Buf b;
+ char *p;
+
+ binit(&b);
+
+ // Create bin directory.
+ p = bpathf(&b, "%s/bin", goroot);
+ if(!isdir(p))
+ xmkdir(p);
+
+ // Create package directory.
+ p = bpathf(&b, "%s/pkg", goroot);
+ if(!isdir(p))
+ xmkdir(p);
+ p = bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch);
+ if(rebuildall)
+ xremoveall(p);
+ xmkdirall(p);
+ if(!streq(goos, gohostos) || !streq(goarch, gohostarch)) {
+ p = bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch);
+ if(rebuildall)
+ xremoveall(p);
+ xmkdirall(p);
+ }
+
+ // Create object directory.
+ // We keep it in pkg/ so that all the generated binaries
+ // are in one tree. If pkg/obj/libgc.a exists, it is a dreg from
+ // before we used subdirectories of obj. Delete all of obj
+ // to clean up.
+ bpathf(&b, "%s/pkg/obj/libgc.a", goroot);
+ if(isfile(bstr(&b)))
+ xremoveall(bpathf(&b, "%s/pkg/obj", goroot));
+ p = bpathf(&b, "%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch);
+ 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(bpathf(&b, "%s/bin/tool", goroot));
+
+ // Remove old pre-tool binaries.
+ for(i=0; i<nelem(oldtool); i++)
+ xremove(bpathf(&b, "%s/bin/%s", goroot, oldtool[i]));
+
+ // If $GOBIN is set and has a Go compiler, it must be cleaned.
+ for(i=0; gochars[i]; i++) {
+ if(isfile(bprintf(&b, "%s%s%c%s", gobin, slash, gochars[i], "g"))) {
+ for(i=0; i<nelem(oldtool); i++)
+ xremove(bprintf(&b, "%s%s%s", gobin, slash, oldtool[i]));
+ break;
+ }
+ }
+
+ // For release, make sure excluded things are excluded.
+ if(hasprefix(goversion, "release.") || hasprefix(goversion, "go.")) {
+ for(i=0; i<nelem(unreleased); i++)
+ if(isdir(bpathf(&b, "%s/%s", goroot, unreleased[i])))
+ fatal("%s should not exist in release build", bstr(&b));
+ }
+
+ bfree(&b);
+}
+
+/*
+ * C library and tool building
+ */
+
+// gccargs is the gcc command line to use for compiling a single C file.
+static char *proto_gccargs[] = {
+ "-Wall",
+ "-Wno-sign-compare",
+ "-Wno-missing-braces",
+ "-Wno-parentheses",
+ "-Wno-unknown-pragmas",
+ "-Wno-switch",
+ "-Wno-comment",
+ "-Werror",
+ "-fno-common",
+ "-ggdb",
+ "-O2",
+};
+
+static Vec gccargs;
+
+// deptab lists changes to the default dependencies for a given prefix.
+// deps ending in /* read the whole directory; deps beginning with -
+// exclude files with that prefix.
+static struct {
+ char *prefix; // prefix of target
+ char *dep[20]; // dependency tweaks for targets with that prefix
+} deptab[] = {
+ {"lib9", {
+ "$GOROOT/include/u.h",
+ "$GOROOT/include/utf.h",
+ "$GOROOT/include/fmt.h",
+ "$GOROOT/include/libc.h",
+ "fmt/*",
+ "utf/*",
+ }},
+ {"libbio", {
+ "$GOROOT/include/u.h",
+ "$GOROOT/include/utf.h",
+ "$GOROOT/include/fmt.h",
+ "$GOROOT/include/libc.h",
+ "$GOROOT/include/bio.h",
+ }},
+ {"libmach", {
+ "$GOROOT/include/u.h",
+ "$GOROOT/include/utf.h",
+ "$GOROOT/include/fmt.h",
+ "$GOROOT/include/libc.h",
+ "$GOROOT/include/bio.h",
+ "$GOROOT/include/ar.h",
+ "$GOROOT/include/bootexec.h",
+ "$GOROOT/include/mach.h",
+ "$GOROOT/include/ureg_amd64.h",
+ "$GOROOT/include/ureg_arm.h",
+ "$GOROOT/include/ureg_x86.h",
+ }},
+ {"cmd/cc", {
+ "-pgen.c",
+ "-pswt.c",
+ }},
+ {"cmd/gc", {
+ "-cplx.c",
+ "-pgen.c",
+ "-y1.tab.c", // makefile dreg
+ "opnames.h",
+ }},
+ {"cmd/5c", {
+ "../cc/pgen.c",
+ "../cc/pswt.c",
+ "../5l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libcc.a",
+ }},
+ {"cmd/6c", {
+ "../cc/pgen.c",
+ "../cc/pswt.c",
+ "../6l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libcc.a",
+ }},
+ {"cmd/8c", {
+ "../cc/pgen.c",
+ "../cc/pswt.c",
+ "../8l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libcc.a",
+ }},
+ {"cmd/5g", {
+ "../gc/cplx.c",
+ "../gc/pgen.c",
+ "../5l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
+ }},
+ {"cmd/6g", {
+ "../gc/cplx.c",
+ "../gc/pgen.c",
+ "../6l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
+ }},
+ {"cmd/8g", {
+ "../gc/cplx.c",
+ "../gc/pgen.c",
+ "../8l/enam.c",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
+ }},
+ {"cmd/5l", {
+ "../ld/data.c",
+ "../ld/elf.c",
+ "../ld/go.c",
+ "../ld/ldelf.c",
+ "../ld/ldmacho.c",
+ "../ld/ldpe.c",
+ "../ld/lib.c",
+ "../ld/symtab.c",
+ "enam.c",
+ }},
+ {"cmd/6l", {
+ "../ld/*",
+ "enam.c",
+ }},
+ {"cmd/8l", {
+ "../ld/*",
+ "enam.c",
+ }},
+ {"cmd/", {
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libmach.a",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/libbio.a",
+ "$GOROOT/pkg/obj/$GOOS_$GOARCH/lib9.a",
+ }},
+ {"pkg/runtime", {
+ "zasm_$GOOS_$GOARCH.h",
+ "zgoarch_$GOARCH.go",
+ "zgoos_$GOOS.go",
+ "zruntime_defs_$GOOS_$GOARCH.go",
+ "zversion.go",
+ }},
+};
+
+// depsuffix records the allowed suffixes for source files.
+char *depsuffix[] = {
+ ".c",
+ ".h",
+ ".s",
+ ".go",
+ ".goc",
+};
+
+// gentab records how to generate some trivial files.
+static struct {
+ char *nameprefix;
+ void (*gen)(char*, char*);
+} gentab[] = {
+ {"opnames.h", gcopnames},
+ {"enam.c", mkenam},
+ {"zasm_", mkzasm},
+ {"zgoarch_", mkzgoarch},
+ {"zgoos_", mkzgoos},
+ {"zruntime_defs_", mkzruntimedefs},
+ {"zversion.go", mkzversion},
+};
+
+// install installs the library, package, or binary associated with dir,
+// which is relative to $GOROOT/src.
+static void
+install(char *dir)
+{
+ char *name, *p, *elem, *prefix, *exe;
+ bool islib, ispkg, isgo, stale;
+ Buf b, b1, path;
+ Vec compile, files, link, go, missing, clean, lib, extra;
+ Time ttarg, t;
+ int i, j, k, n, doclean;
+
+ if(vflag) {
+ if(!streq(goos, gohostos) || !streq(goarch, gohostarch))
+ xprintf("%s (%s/%s)\n", dir, goos, goarch);
+ else
+ xprintf("%s\n", dir);
+ }
+
+ binit(&b);
+ binit(&b1);
+ binit(&path);
+ vinit(&compile);
+ vinit(&files);
+ vinit(&link);
+ vinit(&go);
+ vinit(&missing);
+ vinit(&clean);
+ vinit(&lib);
+ vinit(&extra);
+
+ // path = full path to dir.
+ bpathf(&path, "%s/src/%s", goroot, dir);
+ name = lastelem(dir);
+
+ // For misc/prof, copy into the tool directory and we're done.
+ if(hasprefix(dir, "misc/")) {
+ copy(bpathf(&b, "%s/%s", tooldir, name),
+ bpathf(&b1, "%s/misc/%s", goroot, name), 1);
+ goto out;
+ }
+
+ // For release, cmd/prof and cmd/cov are not included.
+ if((streq(dir, "cmd/cov") || streq(dir, "cmd/prof")) && !isdir(bstr(&path))) {
+ if(vflag > 1)
+ xprintf("skipping %s - does not exist\n", dir);
+ goto out;
+ }
+
+ // set up gcc command line on first run.
+ if(gccargs.len == 0) {
+ xgetenv(&b, "CC");
+ if(b.len == 0)
+ bprintf(&b, "gcc");
+ splitfields(&gccargs, bstr(&b));
+ for(i=0; i<nelem(proto_gccargs); i++)
+ vadd(&gccargs, proto_gccargs[i]);
+ if(xstrstr(gccargs.p[0], "clang") != nil) {
+ vadd(&gccargs, "-Wno-dangling-else");
+ vadd(&gccargs, "-Wno-unused-value");
+ }
+ }
+
+ islib = hasprefix(dir, "lib") || streq(dir, "cmd/cc") || streq(dir, "cmd/gc");
+ ispkg = hasprefix(dir, "pkg");
+ isgo = ispkg || streq(dir, "cmd/go") || streq(dir, "cmd/cgo");
+
+ exe = "";
+ if(streq(gohostos, "windows"))
+ exe = ".exe";
+
+ // Start final link command line.
+ // Note: code below knows that link.p[2] is the target.
+ if(islib) {
+ // C library.
+ vadd(&link, "ar");
+ vadd(&link, "rsc");
+ prefix = "";
+ if(!hasprefix(name, "lib"))
+ prefix = "lib";
+ vadd(&link, bpathf(&b, "%s/pkg/obj/%s_%s/%s%s.a", goroot, gohostos, gohostarch, prefix, name));
+ } else if(ispkg) {
+ // Go library (package).
+ vadd(&link, bpathf(&b, "%s/pack", tooldir));
+ vadd(&link, "grc");
+ p = bprintf(&b, "%s/pkg/%s_%s/%s", goroot, goos, goarch, dir+4);
+ *xstrrchr(p, '/') = '\0';
+ xmkdirall(p);
+ vadd(&link, bpathf(&b, "%s/pkg/%s_%s/%s.a", goroot, goos, goarch, dir+4));
+ } else if(streq(dir, "cmd/go") || streq(dir, "cmd/cgo")) {
+ // Go command.
+ vadd(&link, bpathf(&b, "%s/%sl", tooldir, gochar));
+ vadd(&link, "-o");
+ elem = name;
+ if(streq(elem, "go"))
+ elem = "go_bootstrap";
+ vadd(&link, bpathf(&b, "%s/%s%s", tooldir, elem, exe));
+ } else {
+ // C command.
+ // Use gccargs, but ensure that link.p[2] is output file,
+ // as noted above.
+ vadd(&link, gccargs.p[0]);
+ vadd(&link, "-o");
+ vadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe));
+ vcopy(&link, gccargs.p+1, gccargs.len-1);
+ if(streq(gohostarch, "amd64"))
+ vadd(&link, "-m64");
+ else if(streq(gohostarch, "386"))
+ vadd(&link, "-m32");
+ }
+ ttarg = mtime(link.p[2]);
+
+ // Gather files that are sources for this target.
+ // Everything in that directory, and any target-specific
+ // additions.
+ xreaddir(&files, bstr(&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.
+ n = 0;
+ for(i=0; i<files.len; i++) {
+ p = files.p[i];
+ if(hasprefix(p, ".") || (hasprefix(p, "_") && hassuffix(p, ".go")))
+ xfree(p);
+ else
+ files.p[n++] = p;
+ }
+ files.len = n;
+
+ for(i=0; i<nelem(deptab); i++) {
+ if(hasprefix(dir, deptab[i].prefix)) {
+ for(j=0; (p=deptab[i].dep[j])!=nil; j++) {
+ breset(&b1);
+ bwritestr(&b1, p);
+ bsubst(&b1, "$GOROOT", goroot);
+ bsubst(&b1, "$GOOS", goos);
+ bsubst(&b1, "$GOARCH", goarch);
+ p = bstr(&b1);
+ if(hassuffix(p, ".a")) {
+ vadd(&lib, bpathf(&b, "%s", p));
+ continue;
+ }
+ if(hassuffix(p, "/*")) {
+ bpathf(&b, "%s/%s", bstr(&path), p);
+ b.len -= 2;
+ xreaddir(&extra, bstr(&b));
+ bprintf(&b, "%s", p);
+ b.len -= 2;
+ for(k=0; k<extra.len; k++)
+ vadd(&files, bpathf(&b1, "%s/%s", bstr(&b), extra.p[k]));
+ continue;
+ }
+ if(hasprefix(p, "-")) {
+ p++;
+ n = 0;
+ for(k=0; k<files.len; k++) {
+ if(hasprefix(files.p[k], p))
+ xfree(files.p[k]);
+ else
+ files.p[n++] = files.p[k];
+ }
+ files.len = n;
+ continue;
+ }
+ vadd(&files, p);
+ }
+ }
+ }
+ vuniq(&files);
+
+ // Convert to absolute paths.
+ for(i=0; i<files.len; i++) {
+ if(!isabs(files.p[i])) {
+ bpathf(&b, "%s/%s", bstr(&path), files.p[i]);
+ xfree(files.p[i]);
+ files.p[i] = btake(&b);
+ }
+ }
+
+ // Is the target up-to-date?
+ stale = rebuildall;
+ n = 0;
+ for(i=0; i<files.len; i++) {
+ p = files.p[i];
+ for(j=0; j<nelem(depsuffix); j++)
+ if(hassuffix(p, depsuffix[j]))
+ goto ok;
+ xfree(files.p[i]);
+ continue;
+ ok:
+ t = mtime(p);
+ if(t != 0 && !hassuffix(p, ".a") && !shouldbuild(p, dir)) {
+ xfree(files.p[i]);
+ continue;
+ }
+ if(hassuffix(p, ".go"))
+ vadd(&go, p);
+ if(t > ttarg)
+ stale = 1;
+ if(t == 0) {
+ vadd(&missing, p);
+ files.p[n++] = files.p[i];
+ continue;
+ }
+ files.p[n++] = files.p[i];
+ }
+ files.len = n;
+
+ for(i=0; i<lib.len && !stale; i++)
+ if(mtime(lib.p[i]) > ttarg)
+ stale = 1;
+
+ if(!stale)
+ goto out;
+
+ // For package runtime, copy some files into the work space.
+ if(streq(dir, "pkg/runtime")) {
+ copy(bpathf(&b, "%s/arch_GOARCH.h", workdir),
+ bpathf(&b1, "%s/arch_%s.h", bstr(&path), goarch), 0);
+ copy(bpathf(&b, "%s/defs_GOOS_GOARCH.h", workdir),
+ bpathf(&b1, "%s/defs_%s_%s.h", bstr(&path), goos, goarch), 0);
+ copy(bpathf(&b, "%s/os_GOOS.h", workdir),
+ bpathf(&b1, "%s/os_%s.h", bstr(&path), goos), 0);
+ copy(bpathf(&b, "%s/signals_GOOS.h", workdir),
+ bpathf(&b1, "%s/signals_%s.h", bstr(&path), goos), 0);
+ }
+
+ // Generate any missing files; regenerate existing ones.
+ for(i=0; i<files.len; i++) {
+ p = files.p[i];
+ elem = lastelem(p);
+ for(j=0; j<nelem(gentab); j++) {
+ if(hasprefix(elem, gentab[j].nameprefix)) {
+ if(vflag > 1)
+ xprintf("generate %s\n", p);
+ gentab[j].gen(bstr(&path), p);
+ // Do not add generated file to clean list.
+ // In pkg/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;
+ }
+ }
+ // Did not rebuild p.
+ if(find(p, missing.p, missing.len) >= 0)
+ fatal("missing file %s", p);
+ built:;
+ }
+
+ // One more copy for package runtime.
+ // The last batch was required for the generators.
+ // This one is generated.
+ if(streq(dir, "pkg/runtime")) {
+ copy(bpathf(&b, "%s/zasm_GOOS_GOARCH.h", workdir),
+ bpathf(&b1, "%s/zasm_%s_%s.h", bstr(&path), goos, goarch), 0);
+ }
+
+ // Generate .c files from .goc files.
+ if(streq(dir, "pkg/runtime")) {
+ for(i=0; i<files.len; i++) {
+ p = files.p[i];
+ if(!hassuffix(p, ".goc"))
+ continue;
+ // b = path/zp but with _goarch.c instead of .goc
+ bprintf(&b, "%s%sz%s", bstr(&path), slash, lastelem(p));
+ b.len -= 4;
+ bwritef(&b, "_%s.c", goarch);
+ goc2c(p, bstr(&b));
+ vadd(&files, bstr(&b));
+ }
+ vuniq(&files);
+ }
+
+ if((!streq(goos, gohostos) || !streq(goarch, gohostarch)) && isgo) {
+ // We've generated the right files; the go command can do the build.
+ if(vflag > 1)
+ xprintf("skip build for cross-compile %s\n", dir);
+ goto nobuild;
+ }
+
+ // Compile the files.
+ for(i=0; i<files.len; i++) {
+ if(!hassuffix(files.p[i], ".c") && !hassuffix(files.p[i], ".s"))
+ continue;
+ name = lastelem(files.p[i]);
+
+ vreset(&compile);
+ if(!isgo) {
+ // C library or tool.
+ vcopy(&compile, gccargs.p, gccargs.len);
+ vadd(&compile, "-c");
+ if(streq(gohostarch, "amd64"))
+ vadd(&compile, "-m64");
+ else if(streq(gohostarch, "386"))
+ vadd(&compile, "-m32");
+ if(streq(dir, "lib9"))
+ vadd(&compile, "-DPLAN9PORT");
+
+ vadd(&compile, "-I");
+ vadd(&compile, bpathf(&b, "%s/include", goroot));
+
+ vadd(&compile, "-I");
+ vadd(&compile, bstr(&path));
+
+ // lib9/goos.c gets the default constants hard-coded.
+ if(streq(name, "goos.c")) {
+ vadd(&compile, bprintf(&b, "-DGOOS=\"%s\"", goos));
+ vadd(&compile, bprintf(&b, "-DGOARCH=\"%s\"", goarch));
+ bprintf(&b1, "%s", goroot_final);
+ bsubst(&b1, "\\", "\\\\"); // turn into C string
+ vadd(&compile, bprintf(&b, "-DGOROOT=\"%s\"", bstr(&b1)));
+ vadd(&compile, bprintf(&b, "-DGOVERSION=\"%s\"", goversion));
+ }
+
+ // gc/lex.c records the GOEXPERIMENT setting used during the build.
+ if(streq(name, "lex.c")) {
+ xgetenv(&b, "GOEXPERIMENT");
+ vadd(&compile, bprintf(&b1, "-DGOEXPERIMENT=\"%s\"", bstr(&b)));
+ }
+ } else {
+ // Supporting files for a Go package.
+ if(hassuffix(files.p[i], ".s"))
+ vadd(&compile, bpathf(&b, "%s/%sa", tooldir, gochar));
+ else {
+ vadd(&compile, bpathf(&b, "%s/%sc", tooldir, gochar));
+ vadd(&compile, "-FVw");
+ }
+ vadd(&compile, "-I");
+ vadd(&compile, workdir);
+ vadd(&compile, bprintf(&b, "-DGOOS_%s", goos));
+ vadd(&compile, bprintf(&b, "-DGOARCH_%s", goarch));
+ }
+
+ bpathf(&b, "%s/%s", workdir, lastelem(files.p[i]));
+ doclean = 1;
+ if(!isgo && streq(gohostos, "darwin")) {
+ // To debug C programs on OS X, it is not enough to say -ggdb
+ // on the command line. You have to leave the object files
+ // lying around too. Leave them in pkg/obj/, which does not
+ // get removed when this tool exits.
+ bpathf(&b1, "%s/pkg/obj/%s", goroot, dir);
+ xmkdirall(bstr(&b1));
+ bpathf(&b, "%s/%s", bstr(&b1), lastelem(files.p[i]));
+ doclean = 0;
+ }
+
+ b.p[b.len-1] = 'o'; // was c or s
+ vadd(&compile, "-o");
+ vadd(&compile, bstr(&b));
+ vadd(&compile, files.p[i]);
+ bgrunv(bstr(&path), CheckExit, &compile);
+
+ vadd(&link, bstr(&b));
+ if(doclean)
+ vadd(&clean, bstr(&b));
+ }
+ bgwait();
+
+ if(isgo) {
+ // The last loop was compiling individual files.
+ // Hand the Go files to the compiler en masse.
+ vreset(&compile);
+ vadd(&compile, bpathf(&b, "%s/%sg", tooldir, gochar));
+
+ bpathf(&b, "%s/_go_.%s", workdir, gochar);
+ vadd(&compile, "-o");
+ vadd(&compile, bstr(&b));
+ vadd(&clean, bstr(&b));
+ vadd(&link, bstr(&b));
+
+ vadd(&compile, "-p");
+ if(hasprefix(dir, "pkg/"))
+ vadd(&compile, dir+4);
+ else
+ vadd(&compile, "main");
+
+ if(streq(dir, "pkg/runtime"))
+ vadd(&compile, "-+");
+
+ vcopy(&compile, go.p, go.len);
+
+ runv(nil, bstr(&path), CheckExit, &compile);
+ }
+
+ if(!islib && !isgo) {
+ // C binaries need the libraries explicitly, and -lm.
+ vcopy(&link, lib.p, lib.len);
+ vadd(&link, "-lm");
+ }
+
+ // Remove target before writing it.
+ xremove(link.p[2]);
+
+ runv(nil, nil, CheckExit, &link);
+
+nobuild:
+ // In package runtime, we install runtime.h and cgocall.h too,
+ // for use by cgo compilation.
+ if(streq(dir, "pkg/runtime")) {
+ copy(bpathf(&b, "%s/pkg/%s_%s/cgocall.h", goroot, goos, goarch),
+ bpathf(&b1, "%s/src/pkg/runtime/cgocall.h", goroot), 0);
+ copy(bpathf(&b, "%s/pkg/%s_%s/runtime.h", goroot, goos, goarch),
+ bpathf(&b1, "%s/src/pkg/runtime/runtime.h", goroot), 0);
+ }
+
+
+out:
+ for(i=0; i<clean.len; i++)
+ xremove(clean.p[i]);
+
+ bfree(&b);
+ bfree(&b1);
+ bfree(&path);
+ vfree(&compile);
+ vfree(&files);
+ vfree(&link);
+ vfree(&go);
+ vfree(&missing);
+ vfree(&clean);
+ vfree(&lib);
+ vfree(&extra);
+}
+
+// matchfield reports whether the field matches this build.
+static bool
+matchfield(char *f)
+{
+ return streq(f, goos) || streq(f, goarch) || streq(f, "cmd_go_bootstrap");
+}
+
+// shouldbuild reports whether we should build this file.
+// It applies the same rules that are used with context tags
+// in package go/build, except that the GOOS and GOARCH
+// can appear anywhere in the file name, not just after _.
+// In particular, they can be the entire file name (like windows.c).
+// We also allow the special tag cmd_go_bootstrap.
+// See ../go/bootstrap.go and package go/build.
+static bool
+shouldbuild(char *file, char *dir)
+{
+ char *name, *p;
+ int i, j, ret;
+ Buf b;
+ Vec lines, fields;
+
+ // Check file name for GOOS or GOARCH.
+ name = lastelem(file);
+ for(i=0; i<nelem(okgoos); i++)
+ if(contains(name, okgoos[i]) && !streq(okgoos[i], goos))
+ return 0;
+ for(i=0; i<nelem(okgoarch); i++)
+ if(contains(name, okgoarch[i]) && !streq(okgoarch[i], goarch))
+ return 0;
+
+ // Omit test files.
+ if(contains(name, "_test"))
+ return 0;
+
+ // cmd/go/doc.go has a giant /* */ comment before
+ // it gets to the important detail that it is not part of
+ // package main. We don't parse those comments,
+ // so special case that file.
+ if(hassuffix(file, "cmd/go/doc.go") || hassuffix(file, "cmd\\go\\doc.go"))
+ return 0;
+ if(hassuffix(file, "cmd/cgo/doc.go") || hassuffix(file, "cmd\\cgo\\doc.go"))
+ return 0;
+
+ // Check file contents for // +build lines.
+ binit(&b);
+ vinit(&lines);
+ vinit(&fields);
+
+ ret = 1;
+ readfile(&b, file);
+ splitlines(&lines, bstr(&b));
+ for(i=0; i<lines.len; i++) {
+ p = lines.p[i];
+ while(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
+ p++;
+ if(*p == '\0')
+ continue;
+ if(contains(p, "package documentation")) {
+ ret = 0;
+ goto out;
+ }
+ if(contains(p, "package main") && !streq(dir, "cmd/go") && !streq(dir, "cmd/cgo")) {
+ ret = 0;
+ goto out;
+ }
+ if(!hasprefix(p, "//"))
+ break;
+ if(!contains(p, "+build"))
+ continue;
+ splitfields(&fields, lines.p[i]);
+ if(fields.len < 2 || !streq(fields.p[1], "+build"))
+ continue;
+ for(j=2; j<fields.len; j++) {
+ p = fields.p[j];
+ if((*p == '!' && !matchfield(p+1)) || matchfield(p))
+ goto fieldmatch;
+ }
+ ret = 0;
+ goto out;
+ fieldmatch:;
+ }
+
+out:
+ bfree(&b);
+ vfree(&lines);
+ vfree(&fields);
+
+ return ret;
+}
+
+// copy copies the file src to dst, via memory (so only good for small files).
+static void
+copy(char *dst, char *src, int exec)
+{
+ Buf b;
+
+ if(vflag > 1)
+ xprintf("cp %s %s\n", src, dst);
+
+ binit(&b);
+ readfile(&b, src);
+ writefile(&b, dst, exec);
+ bfree(&b);
+}
+
+// buildorder records the order of builds for the 'go bootstrap' command.
+static char *buildorder[] = {
+ "lib9",
+ "libbio",
+ "libmach",
+
+ "misc/pprof",
+
+ "cmd/cov",
+ "cmd/nm",
+ "cmd/pack",
+ "cmd/prof",
+
+ "cmd/cc", // must be before c
+ "cmd/gc", // must be before g
+ "cmd/%sl", // must be before a, c, g
+ "cmd/%sa",
+ "cmd/%sc",
+ "cmd/%sg",
+
+ // The dependency order here was copied from a buildscript
+ // back when there were build scripts. Will have to
+ // be maintained by hand, but shouldn't change very
+ // often.
+ "pkg/runtime",
+ "pkg/errors",
+ "pkg/sync/atomic",
+ "pkg/sync",
+ "pkg/io",
+ "pkg/unicode",
+ "pkg/unicode/utf8",
+ "pkg/unicode/utf16",
+ "pkg/bytes",
+ "pkg/math",
+ "pkg/strings",
+ "pkg/strconv",
+ "pkg/bufio",
+ "pkg/sort",
+ "pkg/container/heap",
+ "pkg/encoding/base64",
+ "pkg/syscall",
+ "pkg/time",
+ "pkg/os",
+ "pkg/reflect",
+ "pkg/fmt",
+ "pkg/encoding/json",
+ "pkg/flag",
+ "pkg/path/filepath",
+ "pkg/path",
+ "pkg/io/ioutil",
+ "pkg/log",
+ "pkg/regexp/syntax",
+ "pkg/regexp",
+ "pkg/go/token",
+ "pkg/go/scanner",
+ "pkg/go/ast",
+ "pkg/go/parser",
+ "pkg/go/build",
+ "pkg/os/exec",
+ "pkg/net/url",
+ "pkg/text/template/parse",
+ "pkg/text/template",
+ "pkg/go/doc",
+ "cmd/go",
+};
+
+// cleantab records the directories to clean in 'go clean'.
+// It is bigger than the buildorder because we clean all the
+// compilers but build only the $GOARCH ones.
+static char *cleantab[] = {
+ "cmd/5a",
+ "cmd/5c",
+ "cmd/5g",
+ "cmd/5l",
+ "cmd/6a",
+ "cmd/6c",
+ "cmd/6g",
+ "cmd/6l",
+ "cmd/8a",
+ "cmd/8c",
+ "cmd/8g",
+ "cmd/8l",
+ "cmd/cc",
+ "cmd/cov",
+ "cmd/gc",
+ "cmd/go",
+ "cmd/nm",
+ "cmd/pack",
+ "cmd/prof",
+ "lib9",
+ "libbio",
+ "libmach",
+ "pkg/bufio",
+ "pkg/bytes",
+ "pkg/container/heap",
+ "pkg/encoding/base64",
+ "pkg/encoding/json",
+ "pkg/errors",
+ "pkg/flag",
+ "pkg/fmt",
+ "pkg/go/ast",
+ "pkg/go/build",
+ "pkg/go/doc",
+ "pkg/go/parser",
+ "pkg/go/scanner",
+ "pkg/go/token",
+ "pkg/io",
+ "pkg/io/ioutil",
+ "pkg/log",
+ "pkg/math",
+ "pkg/net/url",
+ "pkg/os",
+ "pkg/os/exec",
+ "pkg/path",
+ "pkg/path/filepath",
+ "pkg/reflect",
+ "pkg/regexp",
+ "pkg/regexp/syntax",
+ "pkg/runtime",
+ "pkg/sort",
+ "pkg/strconv",
+ "pkg/strings",
+ "pkg/sync",
+ "pkg/sync/atomic",
+ "pkg/syscall",
+ "pkg/text/template",
+ "pkg/text/template/parse",
+ "pkg/time",
+ "pkg/unicode",
+ "pkg/unicode/utf16",
+ "pkg/unicode/utf8",
+};
+
+static void
+clean(void)
+{
+ int i, j, k;
+ Buf b, path;
+ Vec dir;
+
+ binit(&b);
+ binit(&path);
+ vinit(&dir);
+
+ for(i=0; i<nelem(cleantab); i++) {
+ bpathf(&path, "%s/src/%s", goroot, cleantab[i]);
+ xreaddir(&dir, bstr(&path));
+ // Remove generated files.
+ for(j=0; j<dir.len; j++) {
+ for(k=0; k<nelem(gentab); k++) {
+ if(hasprefix(dir.p[j], gentab[k].nameprefix))
+ xremove(bpathf(&b, "%s/%s", bstr(&path), dir.p[j]));
+ }
+ }
+ // Remove generated binary named for directory.
+ if(hasprefix(cleantab[i], "cmd/"))
+ xremove(bpathf(&b, "%s/%s", bstr(&path), cleantab[i]+4));
+ }
+
+ if(rebuildall) {
+ // Remove object tree.
+ xremoveall(bpathf(&b, "%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch));
+
+ // Remove installed packages and tools.
+ xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch));
+ xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch));
+ xremoveall(tooldir);
+
+ // Remove cached version info.
+ xremove(bpathf(&b, "%s/VERSION.cache", goroot));
+ }
+
+ bfree(&b);
+ bfree(&path);
+ vfree(&dir);
+}
+
+/*
+ * command implementations
+ */
+
+void
+usage(void)
+{
+ xprintf("usage: go tool dist [command]\n"
+ "Commands are:\n"
+ "\n"
+ "banner print installation banner\n"
+ "bootstrap rebuild everything\n"
+ "clean deletes all built files\n"
+ "env [-p] print environment (-p: include $PATH)\n"
+ "install [dir] install individual directory\n"
+ "version print Go version\n"
+ "\n"
+ "All commands take -v flags to emit extra information.\n"
+ );
+ xexit(2);
+}
+
+// The env command prints the default environment.
+void
+cmdenv(int argc, char **argv)
+{
+ bool pflag;
+ char *sep;
+ Buf b, b1;
+ char *format;
+
+ binit(&b);
+ binit(&b1);
+
+ format = "%s=\"%s\"\n";
+ pflag = 0;
+ ARGBEGIN{
+ case 'p':
+ pflag = 1;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ case 'w':
+ format = "set %s=%s\n";
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ usage();
+
+ xprintf(format, "GOROOT", goroot);
+ xprintf(format, "GOBIN", gobin);
+ xprintf(format, "GOARCH", goarch);
+ xprintf(format, "GOOS", goos);
+ xprintf(format, "GOHOSTARCH", gohostarch);
+ xprintf(format, "GOHOSTOS", gohostos);
+ xprintf(format, "GOTOOLDIR", tooldir);
+ xprintf(format, "GOCHAR", gochar);
+
+ if(pflag) {
+ sep = ":";
+ if(streq(gohostos, "windows"))
+ sep = ";";
+ xgetenv(&b, "PATH");
+ bprintf(&b1, "%s%s%s", gobin, sep, bstr(&b));
+ xprintf(format, "PATH", bstr(&b1));
+ }
+
+ bfree(&b);
+ bfree(&b1);
+}
+
+// The bootstrap command runs a build from scratch,
+// stopping at having installed the go_bootstrap command.
+void
+cmdbootstrap(int argc, char **argv)
+{
+ int i;
+ Buf b;
+ char *oldgoos, *oldgoarch, *oldgochar;
+
+ binit(&b);
+
+ ARGBEGIN{
+ case 'a':
+ rebuildall = 1;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ usage();
+
+ if(rebuildall)
+ clean();
+ goversion = findgoversion();
+ setup();
+
+ // For the main bootstrap, building for host os/arch.
+ oldgoos = goos;
+ oldgoarch = goarch;
+ oldgochar = gochar;
+ goos = gohostos;
+ goarch = gohostarch;
+ gochar = gohostchar;
+ xsetenv("GOARCH", goarch);
+ xsetenv("GOOS", goos);
+
+ for(i=0; i<nelem(buildorder); i++) {
+ install(bprintf(&b, buildorder[i], gohostchar));
+ if(!streq(oldgochar, gohostchar) && xstrstr(buildorder[i], "%s"))
+ install(bprintf(&b, buildorder[i], oldgochar));
+ }
+
+ goos = oldgoos;
+ goarch = oldgoarch;
+ gochar = oldgochar;
+ xsetenv("GOARCH", goarch);
+ xsetenv("GOOS", goos);
+
+ // Build pkg/runtime for actual goos/goarch too.
+ if(!streq(goos, gohostos) || !streq(goarch, gohostarch))
+ install("pkg/runtime");
+
+ bfree(&b);
+}
+
+static char*
+defaulttarg(void)
+{
+ char *p;
+ Buf pwd, src, real_src;
+
+ binit(&pwd);
+ binit(&src);
+ binit(&real_src);
+
+ // xgetwd might return a path with symlinks fully resolved, and if
+ // there happens to be symlinks in goroot, then the hasprefix test
+ // will never succeed. Instead, we use xrealwd to get a canonical
+ // goroot/src before the comparison to avoid this problem.
+ xgetwd(&pwd);
+ p = btake(&pwd);
+ bpathf(&src, "%s/src/", goroot);
+ xrealwd(&real_src, bstr(&src));
+ if(!hasprefix(p, bstr(&real_src)))
+ fatal("current directory %s is not under %s", p, bstr(&real_src));
+ p += real_src.len;
+ // guard againt xrealwd return the directory without the trailing /
+ if(*p == slash[0])
+ p++;
+
+ bfree(&pwd);
+ bfree(&src);
+ bfree(&real_src);
+
+ return p;
+}
+
+// Install installs the list of packages named on the command line.
+void
+cmdinstall(int argc, char **argv)
+{
+ int i;
+
+ ARGBEGIN{
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc == 0)
+ install(defaulttarg());
+
+ for(i=0; i<argc; i++)
+ install(argv[i]);
+}
+
+// Clean deletes temporary objects.
+// Clean -i deletes the installed objects too.
+void
+cmdclean(int argc, char **argv)
+{
+ ARGBEGIN{
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ usage();
+
+ clean();
+}
+
+// Banner prints the 'now you've installed Go' banner.
+void
+cmdbanner(int argc, char **argv)
+{
+ char *pathsep;
+ Buf b, b1, search;
+
+ ARGBEGIN{
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ usage();
+
+ binit(&b);
+ binit(&b1);
+ binit(&search);
+
+ xprintf("\n");
+ xprintf("---\n");
+ xprintf("Installed Go for %s/%s in %s\n", goos, goarch, goroot);
+ xprintf("Installed commands in %s\n", gobin);
+
+ // Check that gobin appears in $PATH.
+ xgetenv(&b, "PATH");
+ pathsep = ":";
+ if(streq(gohostos, "windows"))
+ pathsep = ";";
+ bprintf(&b1, "%s%s%s", pathsep, bstr(&b), pathsep);
+ bprintf(&search, "%s%s%s", pathsep, gobin, pathsep);
+ if(xstrstr(bstr(&b1), bstr(&search)) == nil)
+ xprintf("*** You need to add %s to your PATH.\n", gobin);
+
+ if(streq(gohostos, "darwin")) {
+ xprintf("\n"
+ "On OS X the debuggers must be installed setgrp procmod.\n"
+ "Read and run ./sudo.bash to install the debuggers.\n");
+ }
+
+ if(!streq(goroot_final, goroot)) {
+ xprintf("\n"
+ "The binaries expect %s to be copied or moved to %s\n",
+ goroot, goroot_final);
+ }
+
+ bfree(&b);
+ bfree(&b1);
+ bfree(&search);
+}
+
+// Version prints the Go version.
+void
+cmdversion(int argc, char **argv)
+{
+ ARGBEGIN{
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ usage();
+
+ xprintf("%s\n", goversion);
+}