blob: 17f491af8a16fcc6b682632b0196589340f54925 [file] [log] [blame]
// 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"
/*
* Initialization for any invocation.
*/
// The usual variables.
char *goarch;
char *gobin;
char *gohostarch;
char *gohostos;
char *goos;
char *goroot;
char *workdir;
char *gochar;
char *goroot_final;
char *goversion = "go1"; // TODO: Read correct version
char *slash; // / for unix, \ for windows
char *default_goroot;
static void fixslash(Buf*);
static bool shouldbuild(char*, char*);
static void copy(char*, char*);
// 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.
static 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(default_goroot == nil)
fatal("$GOROOT not set and not available");
bwritestr(&b, default_goroot);
}
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 = bprintf(&b, "%s/include/u.h", goroot);
fixslash(&b);
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);
if(find(gohostarch, okgoarch, nelem(okgoarch)) < 0)
fatal("unknown $GOHOSTARCH %s", gohostarch);
xgetenv(&b, "GOARCH");
if(b.len == 0)
bwritestr(&b, gohostarch);
goarch = btake(&b);
if((i=find(goarch, okgoarch, nelem(okgoarch))) < 0)
fatal("unknown $GOARCH %s", goarch);
bprintf(&b, "%c", gochars[i]);
gochar = btake(&b);
xgetenv(&b, "GOROOT_FINAL");
if(b.len > 0)
goroot_final = btake(&b);
else
goroot_final = goroot;
xsetenv("GOROOT", goroot);
xsetenv("GOARCH", goarch);
xsetenv("GOOS", goos);
// Make the environment more predictable.
xsetenv("LANG", "C");
xsetenv("LANGUAGE", "en_US.UTF8");
workdir = xworkdir();
xatexit(rmworkdir);
bfree(&b);
}
// rmworkdir deletes the work directory.
static void
rmworkdir(void)
{
xprintf("rm -rf %s\n", workdir);
xremoveall(workdir);
}
/*
* 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",
"cgo",
"ebnflint",
"goapi",
"gofix",
"goinstall",
"gomake",
"gopack",
"gopprof",
"gotest",
"gotype",
"govet",
"goyacc",
"quietgcc",
};
// setup sets up the tree for the initial build.
static void
setup(void)
{
int i;
Buf b;
char *p;
binit(&b);
run(&b, nil, 0, "ld", "--version", nil);
if(contains(bstr(&b), "gold") && contains(bstr(&b), " 2.20")) {
fatal("Your system has gold 2.20 installed.\n"
"This version is shipped by Ubuntu even though\n"
"it is known not to work on Ubuntu.\n"
"Binaries built with this linker are likely to fail in mysterious ways.\n"
"\n"
"Run sudo apt-get remove binutils-gold.");
}
// Create tool directory.
p = bprintf(&b, "%s/bin", goroot);
fixslash(&b);
if(!isdir(p))
xmkdir(p);
p = bprintf(&b, "%s/bin/go-tool", goroot);
fixslash(&b);
if(!isdir(p))
xmkdir(p);
// Create package directory.
p = bprintf(&b, "%s/pkg", goroot);
fixslash(&b);
if(!isdir(p))
xmkdir(p);
p = bprintf(&b, "%s/pkg/%s_%s", goroot, goos, goarch);
fixslash(&b);
xremoveall(p);
xmkdir(p);
// Remove old pre-tool binaries.
for(i=0; i<nelem(oldtool); i++)
xremove(bprintf(&b, "%s%s%s%s%s", goroot, slash, "bin", slash, 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;
}
}
bfree(&b);
}
/*
* C library and tool building
*/
// gccargs is the gcc command line to use for compiling a single C file.
static char *gccargs[] = {
"gcc",
"-Wall",
"-Wno-sign-compare",
"-Wno-missing-braces",
"-Wno-parentheses",
"-Wno-unknown-pragmas",
"-Wno-switch",
"-Wno-comment",
"-Werror",
"-fno-common",
"-ggdb",
"-O2",
"-c",
};
// 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/*",
"-utf/mkrunetype",
"-utf\\mkrunetype",
"-utf/runetypebody",
"-utf\\runetypebody",
}},
{"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/lib/libcc.a",
}},
{"cmd/6c", {
"../cc/pgen.c",
"../cc/pswt.c",
"../6l/enam.c",
"$GOROOT/lib/libcc.a",
}},
{"cmd/8c", {
"../cc/pgen.c",
"../cc/pswt.c",
"../8l/enam.c",
"$GOROOT/lib/libcc.a",
}},
{"cmd/5g", {
"../gc/cplx.c",
"../gc/pgen.c",
"../5l/enam.c",
"$GOROOT/lib/libgc.a",
}},
{"cmd/6g", {
"../gc/cplx.c",
"../gc/pgen.c",
"../6l/enam.c",
"$GOROOT/lib/libgc.a",
}},
{"cmd/8g", {
"../gc/cplx.c",
"../gc/pgen.c",
"../8l/enam.c",
"$GOROOT/lib/libgc.a",
}},
{"cmd/5l", {
"../ld/*",
"enam.c",
}},
{"cmd/6l", {
"../ld/*",
"enam.c",
}},
{"cmd/8l", {
"../ld/*",
"enam.c",
}},
{"cmd/", {
"$GOROOT/lib/libmach.a",
"$GOROOT/lib/libbio.a",
"$GOROOT/lib/lib9.a",
}},
};
// depsuffix records the allowed suffixes for source files.
char *depsuffix[] = {
".c",
".h",
".s",
".go",
};
// gentab records how to generate some trivial files.
static struct {
char *name;
void (*gen)(char*, char*);
} gentab[] = {
{"opnames.h", gcopnames},
{"enam.c", mkenam},
};
// 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;
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;
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.
bprintf(&path, "%s/src/%s", goroot, dir);
fixslash(&path);
name = lastelem(dir);
islib = hasprefix(dir, "lib") || streq(dir, "cmd/cc") || streq(dir, "cmd/gc");
ispkg = hasprefix(dir, "pkg");
isgo = ispkg || streq(dir, "cmd/go");
// 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";
bprintf(&b, "%s/lib/%s%s.a", goroot, prefix, name);
fixslash(&b);
vadd(&link, bstr(&b));
} else if(ispkg) {
// Go library (package).
bprintf(&b, "%s/bin/go-tool/pack", goroot);
fixslash(&b);
vadd(&link, bstr(&b));
vadd(&link, "grc");
p = bprintf(&b, "%s/pkg/%s_%s/%s", goroot, goos, goarch, dir+4);
*xstrrchr(p, '/') = '\0';
xmkdirall(p);
bprintf(&b, "%s/pkg/%s_%s/%s.a", goroot, goos, goarch, dir+4);
fixslash(&b);
vadd(&link, bstr(&b));
} else if(streq(dir, "cmd/go")) {
// Go command.
bprintf(&b, "%s/bin/go-tool/%sl", goroot, gochar);
fixslash(&b);
vadd(&link, bstr(&b));
vadd(&link, "-o");
bprintf(&b, "%s/bin/go-tool/go_bootstrap", goroot);
fixslash(&b);
vadd(&link, bstr(&b));
} else {
// C command.
vadd(&link, "gcc");
vadd(&link, "-o");
bprintf(&b, "%s/bin/go-tool/%s", goroot, name);
fixslash(&b);
vadd(&link, bstr(&b));
}
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));
for(i=0; i<nelem(deptab); i++) {
if(hasprefix(dir, deptab[i].prefix)) {
for(j=0; (p=deptab[i].dep[j])!=nil; j++) {
if(hasprefix(p, "$GOROOT/")) {
bprintf(&b1, "%s/%s", goroot, p+8);
p = bstr(&b1);
}
if(hassuffix(p, ".a")) {
vadd(&lib, p);
continue;
}
if(hassuffix(p, "/*")) {
bprintf(&b, "%s/%s", bstr(&path), p);
b.len -= 2;
fixslash(&b);
xreaddir(&extra, bstr(&b));
bprintf(&b, "%s", p);
b.len -= 2;
for(k=0; k<extra.len; k++) {
bprintf(&b1, "%s/%s", bstr(&b), extra.p[k]);
fixslash(&b1);
vadd(&files, bstr(&b1));
}
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])) {
bprintf(&b, "%s/%s", bstr(&path), files.p[i]);
fixslash(&b);
xfree(files.p[i]);
files.p[i] = btake(&b);
}
}
// For package runtime, copy some files into the work space.
if(streq(dir, "pkg/runtime")) {
copy(bprintf(&b, "%s/arch_GOARCH.h", workdir),
bprintf(&b1, "%s/arch_%s.h", bstr(&path), goarch));
copy(bprintf(&b, "%s/defs_GOOS_GOARCH.h", workdir),
bprintf(&b1, "%s/defs_%s_%s.h", bstr(&path), goos, goarch));
copy(bprintf(&b, "%s/os_GOOS.h", workdir),
bprintf(&b1, "%s/os_%s.h", bstr(&path), goos));
copy(bprintf(&b, "%s/signals_GOOS.h", workdir),
bprintf(&b1, "%s/signals_%s.h", bstr(&path), goos));
copy(bprintf(&b, "%s/zasm_GOOS_GOARCH.h", workdir),
bprintf(&b1, "%s/zasm_%s_%s.h", bstr(&path), goos, goarch));
}
// Is the target up-to-date?
stale = 1; // TODO: Decide when 0 is okay.
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 > ttarg)
stale = 1;
if(t == 0) {
vadd(&missing, p);
files.p[n++] = files.p[i];
continue;
}
if(!hassuffix(p, ".a") && !shouldbuild(p, dir)) {
xfree(files.p[i]);
continue;
}
if(hassuffix(p, ".go"))
vadd(&go, p);
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;
// Generate any missing files.
for(i=0; i<missing.len; i++) {
p = missing.p[i];
elem = lastelem(p);
for(j=0; j<nelem(gentab); j++) {
if(streq(gentab[j].name, elem)) {
gentab[j].gen(bstr(&path), p);
vadd(&clean, p);
goto built;
}
}
fatal("missing file %s", p);
built:;
}
// 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, nelem(gccargs));
if(streq(gohostarch, "amd64"))
vadd(&compile, "-m64");
else if(streq(gohostarch, "386"))
vadd(&compile, "-m32");
if(streq(dir, "lib9"))
vadd(&compile, "-DPLAN9PORT");
bprintf(&b, "%s/include", goroot);
fixslash(&b);
vadd(&compile, "-I");
vadd(&compile, bstr(&b));
vadd(&compile, "-I");
vadd(&compile, bstr(&path));
// runtime/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));
vadd(&compile, bprintf(&b, "-DGOROOT=\"%s\"", goroot));
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")) {
bprintf(&b, "%s/bin/go-tool/%sa", goroot, gochar);
fixslash(&b);
vadd(&compile, bstr(&b));
} else {
bprintf(&b, "%s/bin/go-tool/%sc", goroot, gochar);
fixslash(&b);
vadd(&compile, bstr(&b));
vadd(&compile, "-FVw");
}
vadd(&compile, "-I");
vadd(&compile, workdir);
vadd(&compile, bprintf(&b, "-DGOOS_%s", goos));
vadd(&compile, bprintf(&b, "-DGOARCH_%s", goos));
}
bprintf(&b, "%s/%s", workdir, lastelem(files.p[i]));
b.p[b.len-1] = 'o'; // was c or s
fixslash(&b);
vadd(&compile, "-o");
vadd(&compile, bstr(&b));
vadd(&link, bstr(&b));
vadd(&clean, bstr(&b));
vadd(&compile, files.p[i]);
runv(nil, bstr(&path), CheckExit, &compile);
vreset(&compile);
}
if(isgo) {
// The last loop was compiling individual files.
// Hand the Go files to the compiler en masse.
vreset(&compile);
bprintf(&b, "%s/bin/go-tool/%sg", goroot, gochar);
fixslash(&b);
vadd(&compile, bstr(&b));
bprintf(&b, "%s/_go_.%s", workdir, gochar);
fixslash(&b);
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);
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, true;
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;
// 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")) {
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;
}
// fixslash rewrites / to \ when the slash character is \, so that the paths look conventional.
static void
fixslash(Buf *b)
{
int i;
if(slash[0] == '/')
return;
for(i=0; i<b->len; i++)
if(b->p[i] == '/')
b->p[i] = '\\';
}
// copy copies the file src to dst, via memory (so only good for small files).
static void
copy(char *dst, char *src)
{
Buf b;
binit(&b);
readfile(&b, src);
writefile(&b, dst);
bfree(&b);
}
/*
* command implementations
*/
// The env command prints the default environment.
void
cmdenv(int argc, char **argv)
{
USED(argc);
USED(argv);
xprintf("GOROOT=%s\n", goroot);
xprintf("GOARCH=%s\n", goarch);
xprintf("GOOS=%s\n", goos);
}
// buildorder records the order of builds for the 'go bootstrap' command.
static char *buildorder[] = {
"lib9",
"libbio",
"libmach",
"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/encoding/gob",
"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",
"cmd/go",
};
// 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 *p;
setup();
// TODO: nuke();
binit(&b);
for(i=0; i<nelem(buildorder); i++) {
p = bprintf(&b, buildorder[i], gochar);
xprintf("%s\n", p);
install(p);
}
bfree(&b);
}
// Install installs the list of packages named on the command line.
void
cmdinstall(int argc, char **argv)
{
int i;
for(i=1; i<argc; i++)
install(argv[i]);
}