Skip to content
Snippets Groups Projects
build.c 31.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Russ Cox's avatar
    Russ Cox committed
    // 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"
    
    Russ Cox's avatar
    Russ Cox committed
    
    /*
     * Initialization for any invocation.
     */
    
    // The usual variables.
    char *goarch;
    char *gobin;
    char *gohostarch;
    
    char *gohostchar;
    
    Russ Cox's avatar
    Russ Cox committed
    char *gohostos;
    char *goos;
    
    char *goroot = GOROOT_FINAL;
    char *goroot_final = GOROOT_FINAL;
    
    Russ Cox's avatar
    Russ Cox committed
    char *workdir;
    
    char	*tooldir;
    
    Russ Cox's avatar
    Russ Cox committed
    char *gochar;
    
    char *goversion;
    
    Russ Cox's avatar
    Russ Cox committed
    char *slash;	// / for unix, \ for windows
    
    
    bool	rebuildall = 0;
    
    
    Russ Cox's avatar
    Russ Cox committed
    static bool shouldbuild(char*, char*);
    static void copy(char*, char*);
    
    static char *findgoversion(void);
    
    Russ Cox's avatar
    Russ Cox committed
    
    // 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.
    
    Russ Cox's avatar
    Russ Cox committed
    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)
    		goroot = btake(&b);
    
    Russ Cox's avatar
    Russ Cox committed
    
    	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);
    
    Russ Cox's avatar
    Russ Cox committed
    	if(!isfile(p)) {
    		fatal("$GOROOT is not set correctly or not exported\n"
    			"\tGOROOT=%s\n"
    			"\t%s does not exist", goroot, p);
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	xgetenv(&b, "GOHOSTARCH");
    	if(b.len > 0)
    		gohostarch = btake(&b);
    
    
    	i = find(gohostarch, okgoarch, nelem(okgoarch));
    	if(i < 0)
    
    Russ Cox's avatar
    Russ Cox committed
    		fatal("unknown $GOHOSTARCH %s", gohostarch);
    
    	bprintf(&b, "%c", gochars[i]);
    	gohostchar = btake(&b);
    
    Russ Cox's avatar
    Russ Cox committed
    
    	xgetenv(&b, "GOARCH");
    	if(b.len == 0)
    		bwritestr(&b, gohostarch);
    	goarch = btake(&b);
    
    	i = find(goarch, okgoarch, nelem(okgoarch));
    	if(i < 0)
    
    Russ Cox's avatar
    Russ Cox committed
    		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();
    
    
    Russ Cox's avatar
    Russ Cox committed
    	workdir = xworkdir();
    	xatexit(rmworkdir);
    
    
    	bpathf(&b, "%s/pkg/tool/%s_%s", goroot, gohostos, gohostarch);
    	tooldir = btake(&b);
    
    
    Russ Cox's avatar
    Russ Cox committed
    	bfree(&b);
    }
    
    // rmworkdir deletes the work directory.
    static void
    rmworkdir(void)
    {
    
    	if(vflag > 1)
    		xprintf("rm -rf %s\n", workdir);
    
    Russ Cox's avatar
    Russ Cox committed
    	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));
    
    done:
    	p = btake(&b);
    	
    	
    	bfree(&b);
    	bfree(&path);
    	bfree(&bmore);
    	bfree(&branch);
    	vfree(&tags);
    	
    	return p;
    }
    
    
    Russ Cox's avatar
    Russ Cox committed
    /*
     * 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",
    
    Russ Cox's avatar
    Russ Cox committed
    	"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);
    
    
    	// Create bin directory.
    
    	p = bpathf(&b, "%s/bin", goroot);
    
    Russ Cox's avatar
    Russ Cox committed
    	if(!isdir(p))
    		xmkdir(p);
    
    	// Create package directory.
    
    	p = bpathf(&b, "%s/pkg", goroot);
    
    Russ Cox's avatar
    Russ Cox committed
    	if(!isdir(p))
    		xmkdir(p);
    
    	p = bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch);
    	if(rebuildall)
    		xremoveall(p);
    
    	if(!streq(goos, gohostos) || !streq(goarch, gohostarch)) {
    		p = bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch);
    		if(rebuildall)
    			xremoveall(p);
    		xmkdir(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.
    	xremoveall(tooldir);
    	xmkdirall(tooldir);
    
    	// Remove tool binaries from before the tool/gohostos_gohostarch
    	xremoveall(bpathf(&b, "%s/bin/tool", goroot));
    
    Russ Cox's avatar
    Russ Cox committed
    
    	// Remove old pre-tool binaries.
    	for(i=0; i<nelem(oldtool); i++)
    
    		xremove(bpathf(&b, "%s/bin/%s", goroot, oldtool[i]));
    
    
    Russ Cox's avatar
    Russ Cox committed
    	// 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 *proto_gccargs[] = {
    
    Russ Cox's avatar
    Russ Cox committed
    	"-Wall",
    	"-Wno-sign-compare",
    	"-Wno-missing-braces",
    	"-Wno-parentheses",
    	"-Wno-unknown-pragmas",
    	"-Wno-switch",
    	"-Wno-comment",
    	"-Werror",
    	"-fno-common",
    	"-ggdb",
    	"-O2",
    };
    
    
    static Vec gccargs;
    
    
    Russ Cox's avatar
    Russ Cox committed
    // 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",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/6c", {
    		"../cc/pgen.c",
    		"../cc/pswt.c",
    		"../6l/enam.c",
    
    		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libcc.a",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/8c", {
    		"../cc/pgen.c",
    		"../cc/pswt.c",
    		"../8l/enam.c",
    
    		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libcc.a",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/5g", {
    		"../gc/cplx.c",
    		"../gc/pgen.c",
    		"../5l/enam.c",
    
    		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/6g", {
    		"../gc/cplx.c",
    		"../gc/pgen.c",
    		"../6l/enam.c",
    
    		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/8g", {
    		"../gc/cplx.c",
    		"../gc/pgen.c",
    		"../8l/enam.c",
    
    		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    	{"cmd/5l", {
    
    Russ Cox's avatar
    Russ Cox committed
    		"../ld/data.c",
    		"../ld/elf.c",
    		"../ld/go.c",
    		"../ld/ldelf.c",
    		"../ld/ldmacho.c",
    		"../ld/ldpe.c",
    		"../ld/lib.c",
    		"../ld/symtab.c",
    
    Russ Cox's avatar
    Russ Cox committed
    		"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",
    
    Russ Cox's avatar
    Russ Cox committed
    	}},
    };
    
    // depsuffix records the allowed suffixes for source files.
    char *depsuffix[] = {
    	".c",
    	".h",
    	".s",
    	".go",
    
    Russ Cox's avatar
    Russ Cox committed
    };
    
    // gentab records how to generate some trivial files.
    static struct {
    
    	char *nameprefix;
    
    Russ Cox's avatar
    Russ Cox committed
    	void (*gen)(char*, char*);
    } gentab[] = {
    	{"opnames.h", gcopnames},
    	{"enam.c", mkenam},
    
    	{"zasm_", mkzasm},
    	{"zgoarch_", mkzgoarch},
    	{"zgoos_", mkzgoos},
    	{"zruntime_defs_", mkzruntimedefs},
    	{"zversion.go", mkzversion},
    
    Russ Cox's avatar
    Russ Cox committed
    };
    
    // 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;
    
    Russ Cox's avatar
    Russ Cox committed
    	bool islib, ispkg, isgo, stale;
    	Buf b, b1, path;
    	Vec compile, files, link, go, missing, clean, lib, extra;
    	Time ttarg, t;
    
    Russ Cox's avatar
    Russ Cox committed
    	int i, j, k, n, doclean;
    
    Russ Cox's avatar
    Russ Cox committed
    
    
    	if(vflag) {
    		if(!streq(goos, gohostos) || !streq(goarch, gohostarch))
    			xprintf("%s (%s/%s)\n", dir, goos, goarch);
    		else
    			xprintf("%s\n", dir);
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	binit(&b);
    	binit(&b1);
    	binit(&path);
    	vinit(&compile);
    	vinit(&files);
    	vinit(&link);
    	vinit(&go);
    	vinit(&missing);
    	vinit(&clean);
    	vinit(&lib);
    	vinit(&extra);
    	
    
    	// 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]);
    	}
    	
    
    Russ Cox's avatar
    Russ Cox committed
    	// path = full path to dir.
    
    	bpathf(&path, "%s/src/%s", goroot, dir);
    
    Russ Cox's avatar
    Russ Cox committed
    	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") || streq(dir, "cmd/cgo");
    
    Russ Cox's avatar
    Russ Cox committed
    
    
    	exe = "";
    	if(streq(gohostos, "windows"))
    		exe = ".exe";
    
    Russ Cox's avatar
    Russ Cox committed
    	
    	// 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));
    
    Russ Cox's avatar
    Russ Cox committed
    	} else if(ispkg) {
    		// Go library (package).
    
    		vadd(&link, bpathf(&b, "%s/pack", tooldir));
    
    Russ Cox's avatar
    Russ Cox committed
    		vadd(&link, "grc");
    
    		p = bprintf(&b, "%s/pkg/%s_%s/%s", goroot, gohostos, gohostarch, dir+4);
    
    Russ Cox's avatar
    Russ Cox committed
    		*xstrrchr(p, '/') = '\0';
    		xmkdirall(p);
    
    		vadd(&link, bpathf(&b, "%s/pkg/%s_%s/%s.a", goroot, gohostos, gohostarch, dir+4));
    	} else if(streq(dir, "cmd/go") || streq(dir, "cmd/cgo")) {
    
    Russ Cox's avatar
    Russ Cox committed
    		// Go command.
    
    		vadd(&link, bpathf(&b, "%s/%sl", tooldir, gochar));
    
    Russ Cox's avatar
    Russ Cox committed
    		vadd(&link, "-o");
    
    		elem = name;
    		if(streq(elem, "go"))
    			elem = "go_bootstrap";
    		vadd(&link, bpathf(&b, "%s/%s%s", tooldir, elem, exe));
    
    Russ Cox's avatar
    Russ Cox committed
    	} else {
    		// C command.
    
    		// Use gccargs, but ensure that link.p[2] is output file,
    		// as noted above.
    		vadd(&link, gccargs.p[0]);
    
    Russ Cox's avatar
    Russ Cox committed
    		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");
    
    Russ Cox's avatar
    Russ Cox committed
    	}
    	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;
    
    
    Russ Cox's avatar
    Russ Cox committed
    	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);
    
    Russ Cox's avatar
    Russ Cox committed
    				if(hassuffix(p, ".a")) {
    
    					vadd(&lib, bpathf(&b, "%s", p));
    
    Russ Cox's avatar
    Russ Cox committed
    					continue;
    				}
    				if(hassuffix(p, "/*")) {
    
    					bpathf(&b, "%s/%s", bstr(&path), p);
    
    Russ Cox's avatar
    Russ Cox committed
    					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]));
    
    Russ Cox's avatar
    Russ Cox committed
    					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);
    
    Russ Cox's avatar
    Russ Cox committed
    	// 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]);
    
    Russ Cox's avatar
    Russ Cox committed
    			xfree(files.p[i]);
    			files.p[i] = btake(&b);
    		}
    	}
    
    	// Is the target up-to-date?
    
    	stale = rebuildall;
    
    Russ Cox's avatar
    Russ Cox committed
    	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);
    
    Russ Cox's avatar
    Russ Cox committed
    		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));
    		copy(bpathf(&b, "%s/defs_GOOS_GOARCH.h", workdir),
    			bpathf(&b1, "%s/defs_%s_%s.h", bstr(&path), goos, goarch));
    		copy(bpathf(&b, "%s/os_GOOS.h", workdir),
    			bpathf(&b1, "%s/os_%s.h", bstr(&path), goos));
    		copy(bpathf(&b, "%s/signals_GOOS.h", workdir),
    			bpathf(&b1, "%s/signals_%s.h", bstr(&path), goos));
    	}
    
    	// Generate any missing files; regenerate existing ones.
    	for(i=0; i<files.len; i++) {
    		p = files.p[i];
    
    Russ Cox's avatar
    Russ Cox committed
    		elem = lastelem(p);
    		for(j=0; j<nelem(gentab); j++) {
    
    			if(hasprefix(elem, gentab[j].nameprefix)) {
    				if(vflag > 1)
    					xprintf("generate %s\n", p);
    
    Russ Cox's avatar
    Russ Cox committed
    				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.
    
    Russ Cox's avatar
    Russ Cox committed
    				goto built;
    			}
    		}
    
    		// Did not rebuild p.
    		if(find(p, missing.p, missing.len) >= 0)
    			fatal("missing file %s", p);
    
    Russ Cox's avatar
    Russ Cox committed
    	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));
    	}
    	
    	// 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)) {
    		// 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;
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	// 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");
    
    Russ Cox's avatar
    Russ Cox committed
    			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));
    
    Russ Cox's avatar
    Russ Cox committed
    			
    			vadd(&compile, "-I");
    			vadd(&compile, bstr(&path));
    	
    
    			// lib9/goos.c gets the default constants hard-coded.
    
    Russ Cox's avatar
    Russ Cox committed
    			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)));
    
    Russ Cox's avatar
    Russ Cox committed
    				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));
    
    				vadd(&compile, bpathf(&b, "%s/%sc", tooldir, gochar));
    
    Russ Cox's avatar
    Russ Cox committed
    				vadd(&compile, "-FVw");
    			}
    			vadd(&compile, "-I");
    			vadd(&compile, workdir);
    			vadd(&compile, bprintf(&b, "-DGOOS_%s", goos));
    
    			vadd(&compile, bprintf(&b, "-DGOARCH_%s", goarch));
    
    Russ Cox's avatar
    Russ Cox committed
    		}	
    
    
    Russ Cox's avatar
    Russ Cox committed
    		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]));
    
    Russ Cox's avatar
    Russ Cox committed
    			doclean = 0;
    		}
    
    Russ Cox's avatar
    Russ Cox committed
    		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);
    
    Russ Cox's avatar
    Russ Cox committed
    
    
    		vadd(&link, bstr(&b));
    
    Russ Cox's avatar
    Russ Cox committed
    		if(doclean)
    			vadd(&clean, bstr(&b));
    
    Russ Cox's avatar
    Russ Cox committed
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	
    	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));
    
    Russ Cox's avatar
    Russ Cox committed
    
    
    		bpathf(&b, "%s/_go_.%s", workdir, gochar);
    
    Russ Cox's avatar
    Russ Cox committed
    		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));
    		copy(bpathf(&b, "%s/pkg/%s_%s/runtime.h", goroot, goos, goarch),
    			bpathf(&b1, "%s/src/pkg/runtime/runtime.h", goroot));
    	}
    
    
    
    Russ Cox's avatar
    Russ Cox committed
    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;
    
    Russ Cox's avatar
    Russ Cox committed
    	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;
    
    Russ Cox's avatar
    Russ Cox committed
    
    	// 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")) {
    
    Russ Cox's avatar
    Russ Cox committed
    			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;