| // Copyright 2009 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. | |
| // Build a collection of go programs into a single package. | |
| #include <u.h> | |
| #include <unistd.h> | |
| #include <libc.h> | |
| #include <bio.h> | |
| void | |
| usage(void) | |
| { | |
| fprint(2, "usage: gobuild [-m] [packagename...]\n"); | |
| exits("usage"); | |
| } | |
| int chatty; | |
| int devnull; // fd of /dev/null | |
| int makefile; // generate Makefile | |
| char *thechar; // object character | |
| char *goos; | |
| char *goarch; | |
| char *goroot; | |
| char **oargv; | |
| int oargc; | |
| void writemakefile(void); | |
| int sourcefilenames(char***); | |
| void* | |
| emalloc(int n) | |
| { | |
| void *v; | |
| v = malloc(n); | |
| if(v == nil) | |
| sysfatal("out of memory"); | |
| memset(v, 0, n); | |
| return v; | |
| } | |
| void* | |
| erealloc(void *v, int n) | |
| { | |
| v = realloc(v, n); | |
| if(v == nil) | |
| sysfatal("out of memory"); | |
| return v; | |
| } | |
| // Info about when to compile a particular file. | |
| typedef struct Job Job; | |
| struct Job | |
| { | |
| char *name; | |
| char *pkg; | |
| int pass; | |
| }; | |
| Job *job; | |
| int njob; | |
| char **pkg; | |
| int npkg; | |
| // Run the command in argv. | |
| // Return -1 if it fails (non-zero exit status). | |
| // Return 0 on success. | |
| // Showoutput controls whether to let output from command display | |
| // on standard output and standard error. | |
| int | |
| run(char **argv, int showoutput) | |
| { | |
| int pid, i; | |
| Waitmsg *w; | |
| vlong n0, n1; | |
| char buf[100]; | |
| n0 = nsec(); | |
| pid = fork(); | |
| if(pid < 0) | |
| sysfatal("fork: %r"); | |
| if(pid == 0){ | |
| dup(devnull, 0); | |
| if(!showoutput) | |
| dup(devnull, 2); | |
| dup(2, 1); | |
| if(devnull > 2) | |
| close(devnull); | |
| exec(argv[0], argv); | |
| fprint(2, "exec %s: %r\n", argv[0]); | |
| exit(1); | |
| } | |
| while((w = waitfor(pid)) == nil) { | |
| rerrstr(buf, sizeof buf); | |
| if(strstr(buf, "interrupt")) | |
| continue; | |
| sysfatal("waitfor %d: %r", pid); | |
| } | |
| n1 = nsec(); | |
| if(chatty > 1){ | |
| fprint(2, "%5.3f", (n1-n0)/1.e9); | |
| for(i=0; argv[i]; i++) | |
| fprint(2, " %s", argv[i]); | |
| if(w->msg[0]) | |
| fprint(2, " [%s]", w->msg); | |
| fprint(2, "\n"); | |
| } | |
| if(w->msg[0]) | |
| return -1; | |
| return 0; | |
| } | |
| // Build the file using the compiler cc. | |
| // Return -1 on error, 0 on success. | |
| // If show is set, print the command and the output. | |
| int | |
| buildcc(char *cc, char *file, int show) | |
| { | |
| char *argv[3]; | |
| if(show) | |
| fprint(2, "$ %s %s\n", cc, file); | |
| argv[0] = cc; | |
| argv[1] = file; | |
| argv[2] = nil; | |
| return run(argv, show); | |
| } | |
| // Run ar to add the given files to pkg.a. | |
| void | |
| ar(char *pkg, char **file, int nfile) | |
| { | |
| char **arg; | |
| int i, n; | |
| char sixar[20]; | |
| char pkga[1000]; | |
| arg = emalloc((4+nfile)*sizeof arg[0]); | |
| n = 0; | |
| snprint(sixar, sizeof sixar, "%sar", thechar); | |
| snprint(pkga, sizeof pkga, "%s.a", pkg); | |
| arg[n++] = sixar; | |
| arg[n++] = "grc"; | |
| arg[n++] = pkga; | |
| for(i=0; i<nfile; i++) | |
| arg[n++] = file[i]; | |
| arg[n] = nil; | |
| if(run(arg, 1) < 0) | |
| sysfatal("ar: %r"); | |
| } | |
| // Return bool whether s ends in suffix. | |
| int | |
| suffix(char *s, char *suffix) | |
| { | |
| int n1, n2; | |
| n1 = strlen(s); | |
| n2 = strlen(suffix); | |
| if(n1>n2 && strcmp(s+n1-n2, suffix) == 0) | |
| return 1; | |
| return 0; | |
| } | |
| // Return the name of the compiler for file. | |
| char* | |
| compiler(char *file) | |
| { | |
| static char buf[20]; | |
| if(suffix(file, ".go")) | |
| snprint(buf, sizeof buf, "%sg", thechar); | |
| else if(suffix(file, ".c")) | |
| snprint(buf, sizeof buf, "%sc", thechar); | |
| else if(suffix(file, ".s")) | |
| snprint(buf, sizeof buf, "%sa", thechar); | |
| else | |
| sysfatal("don't know how to build %s", file); | |
| return buf; | |
| } | |
| // Return the object name for file, replacing the | |
| // .c or .g or .a with .suffix. | |
| char* | |
| goobj(char *file, char *suffix) | |
| { | |
| char *p; | |
| p = strrchr(file, '.'); | |
| if(p == nil) | |
| sysfatal("don't know object name for %s", file); | |
| return smprint("%.*s.%s", utfnlen(file, p-file), file, suffix); | |
| } | |
| // Figure out package of .go file. | |
| // Maintain list of all packages seen so far. | |
| // Returned package string is in that list, | |
| // so caller can use pointer compares. | |
| char* | |
| getpkg(char *file) | |
| { | |
| Biobuf *b; | |
| char *p, *q; | |
| int i; | |
| if((b = Bopen(file, OREAD)) == nil) | |
| sysfatal("open %s: %r", file); | |
| while((p = Brdline(b, '\n')) != nil) { | |
| p[Blinelen(b)-1] = '\0'; | |
| if(!suffix(file, ".go")) { | |
| if(*p != '/' || *(p+1) != '/') | |
| continue; | |
| p += 2; | |
| } | |
| if(strstr(p, "gobuild: ignore")) | |
| return "main"; | |
| while(*p == ' ' || *p == '\t') | |
| p++; | |
| if(strncmp(p, "package", 7) == 0 && (p[7] == ' ' || p[7] == '\t')) { | |
| p+=7; | |
| while(*p == ' ' || *p == '\t') | |
| p++; | |
| q = p+strlen(p); | |
| while(q > p && (*(q-1) == ' ' || *(q-1) == '\t')) | |
| *--q = '\0'; | |
| for(i=0; i<npkg; i++) { | |
| if(strcmp(pkg[i], p) == 0) { | |
| Bterm(b); | |
| return pkg[i]; | |
| } | |
| } | |
| // don't put main in the package list | |
| if(strcmp(p, "main") == 0) | |
| return "main"; | |
| npkg++; | |
| pkg = erealloc(pkg, npkg*sizeof pkg[0]); | |
| pkg[i] = emalloc(strlen(p)+1); | |
| strcpy(pkg[i], p); | |
| Bterm(b); | |
| return pkg[i]; | |
| } | |
| } | |
| Bterm(b); | |
| return nil; | |
| } | |
| // Format name using $(GOOS) and $(GOARCH). | |
| int | |
| dollarfmt(Fmt *f) | |
| { | |
| char *s; | |
| Rune r; | |
| int n; | |
| s = va_arg(f->args, char*); | |
| if(s == nil){ | |
| fmtstrcpy(f, "<nil>"); | |
| return 0; | |
| } | |
| for(; *s; s+=n){ | |
| n = strlen(goarch); | |
| if(strncmp(s, goarch, n) == 0){ | |
| if(f->flags & FmtSharp) | |
| fmtstrcpy(f, "${GOARCH}"); // shell | |
| else | |
| fmtstrcpy(f, "$(GOARCH)"); // make | |
| continue; | |
| } | |
| n = strlen(goos); | |
| if(strncmp(s, goos, n) == 0){ | |
| if(f->flags & FmtSharp) | |
| fmtstrcpy(f, "${GOOS}"); // shell | |
| else | |
| fmtstrcpy(f, "$(GOOS)"); // make | |
| continue; | |
| } | |
| n = chartorune(&r, s); | |
| fmtrune(f, r); | |
| } | |
| return 0; | |
| } | |
| // Makefile preamble template. | |
| char preamble[] = | |
| "O=%s\n" | |
| "GC=$(O)g\n" | |
| "CC=$(O)c -w\n" | |
| "AS=$(O)a\n" | |
| "AR=$(O)ar\n" | |
| "\n" | |
| "default: packages\n" | |
| "\n" | |
| "clean:\n" | |
| "\trm -f *.$O *.a $O.out\n" | |
| "\n" | |
| "test: packages\n" | |
| "\tgotest\n" | |
| "\n" | |
| "coverage: packages\n" | |
| "\tgotest\n" | |
| "\t6cov -g `pwd` | grep -v '_test\\.go:'\n" | |
| "\n" | |
| "%%.$O: %%.go\n" | |
| "\t$(GC) $*.go\n" | |
| "\n" | |
| "%%.$O: %%.c\n" | |
| "\t$(CC) $*.c\n" | |
| "\n" | |
| "%%.$O: %%.s\n" | |
| "\t$(AS) $*.s\n" | |
| "\n" | |
| ; | |
| void | |
| writemakefile(void) | |
| { | |
| Biobuf bout; | |
| vlong o; | |
| int i, k, l, pass; | |
| char **obj; | |
| int nobj; | |
| // Write makefile. | |
| Binit(&bout, 1, OWRITE); | |
| Bprint(&bout, "# DO NOT EDIT. Automatically generated by gobuild.\n"); | |
| o = Boffset(&bout); | |
| Bprint(&bout, "#"); | |
| for(i=0; i<oargc; i++){ | |
| if(Boffset(&bout) - o > 60){ | |
| Bprint(&bout, "\\\n# "); | |
| o = Boffset(&bout); | |
| } | |
| Bprint(&bout, " %#$", oargv[i]); | |
| } | |
| Bprint(&bout, " >Makefile\n"); | |
| Bprint(&bout, preamble, thechar); | |
| // O2=\ | |
| // os_file.$O\ | |
| // os_time.$O\ | |
| // | |
| obj = emalloc(njob*sizeof obj[0]); | |
| for(pass=0;; pass++) { | |
| nobj = 0; | |
| for(i=0; i<njob; i++) | |
| if(job[i].pass == pass) | |
| obj[nobj++] = goobj(job[i].name, "$O"); | |
| if(nobj == 0) | |
| break; | |
| Bprint(&bout, "O%d=\\\n", pass+1); | |
| for(i=0; i<nobj; i++) | |
| Bprint(&bout, "\t%$\\\n", obj[i]); | |
| Bprint(&bout, "\n"); | |
| } | |
| // math.a: a1 a2 | |
| for(i=0; i<npkg; i++) { | |
| Bprint(&bout, "%s.a:", pkg[i]); | |
| for(k=0; k<pass; k++) | |
| Bprint(&bout, " a%d", k+1); | |
| Bprint(&bout, "\n"); | |
| } | |
| Bprint(&bout, "\n"); | |
| // a1: $(O1) | |
| // $(AS) grc $(PKG) $(O1) | |
| // rm -f $(O1) | |
| for(k=0; k<pass; k++){ | |
| Bprint(&bout, "a%d:\t$(O%d)\n", k+1, k+1); | |
| for(i=0; i<npkg; i++) { | |
| nobj = 0; | |
| for(l=0; l<njob; l++) | |
| if(job[l].pass == k && job[l].pkg == pkg[i]) | |
| obj[nobj++] = goobj(job[l].name, "$O"); | |
| if(nobj > 0) { | |
| Bprint(&bout, "\t$(AR) grc %s.a", pkg[i]); | |
| for(l=0; l<nobj; l++) | |
| Bprint(&bout, " %$", obj[l]); | |
| Bprint(&bout, "\n"); | |
| } | |
| } | |
| Bprint(&bout, "\trm -f $(O%d)\n", k+1); | |
| Bprint(&bout, "\n"); | |
| } | |
| // newpkg: clean | |
| // 6ar grc pkg.a | |
| Bprint(&bout, "newpkg: clean\n"); | |
| for(i=0; i<npkg; i++) | |
| Bprint(&bout, "\t$(AR) grc %s.a\n", pkg[i]); | |
| Bprint(&bout, "\n"); | |
| // $(O1): newpkg | |
| // $(O2): a1 | |
| Bprint(&bout, "$(O1): newpkg\n"); | |
| for(i=1; i<pass; i++) | |
| Bprint(&bout, "$(O%d): a%d\n", i+1, i); | |
| Bprint(&bout, "\n"); | |
| // nuke: clean | |
| // rm -f $(GOROOT)/pkg/xxx.a | |
| Bprint(&bout, "nuke: clean\n"); | |
| Bprint(&bout, "\trm -f"); | |
| for(i=0; i<npkg; i++) | |
| Bprint(&bout, " $(GOROOT)/pkg/%s.a", pkg[i]); | |
| Bprint(&bout, "\n\n"); | |
| // packages: pkg.a | |
| // rm -f $(GOROOT)/pkg/xxx.a | |
| Bprint(&bout, "packages:"); | |
| for(i=0; i<npkg; i++) | |
| Bprint(&bout, " %s.a", pkg[i]); | |
| Bprint(&bout, "\n\n"); | |
| // install: packages | |
| // cp xxx.a $(GOROOT)/pkg/xxx.a | |
| Bprint(&bout, "install: packages\n"); | |
| for(i=0; i<npkg; i++) | |
| Bprint(&bout, "\tcp %s.a $(GOROOT)/pkg/%s.a\n", pkg[i], pkg[i]); | |
| Bprint(&bout, "\n"); | |
| Bterm(&bout); | |
| } | |
| int | |
| sourcefilenames(char ***argvp) | |
| { | |
| Dir *d; | |
| int dir, nd, i, argc; | |
| char **argv; | |
| if((dir = open(".", OREAD)) < 0) | |
| sysfatal("open .: %r"); | |
| nd = dirreadall(dir, &d); | |
| close(dir); | |
| argv = emalloc((nd+1)*sizeof argv[0]); | |
| argc = 0; | |
| for(i=0; i<nd; i++) { | |
| if(suffix(d[i].name, ".go") | |
| || suffix(d[i].name, ".c") | |
| || suffix(d[i].name, ".s")) | |
| argv[argc++] = d[i].name; | |
| } | |
| *argvp = argv; | |
| argv[argc] = nil; | |
| return argc; | |
| } | |
| void | |
| main(int argc, char **argv) | |
| { | |
| int i, k, pass, npending, nfail, nsuccess, narfiles; | |
| Job **pending, **fail, **success, *j; | |
| char **arfiles; | |
| oargc = argc; | |
| oargv = argv; | |
| fmtinstall('$', dollarfmt); | |
| goos = getenv("GOOS"); | |
| if(goos == nil) | |
| sysfatal("no $GOOS"); | |
| goarch = getenv("GOARCH"); | |
| if(goarch == nil) | |
| sysfatal("no $GOARCH"); | |
| if(strcmp(goarch, "amd64") == 0) | |
| thechar = "6"; | |
| else | |
| sysfatal("unknown $GOARCH"); | |
| devnull = open("/dev/null", OWRITE); | |
| if(devnull < 0) | |
| sysfatal("open /dev/null: %r"); | |
| goroot = getenv("GOROOT"); | |
| if(goroot == nil) | |
| sysfatal("no $GOROOT"); | |
| ARGBEGIN{ | |
| default: | |
| usage(); | |
| case 'm': | |
| makefile = 1; | |
| break; | |
| case 'v': | |
| chatty++; | |
| break; | |
| }ARGEND | |
| // If no arguments, use all source files in current directory. | |
| if(argc == 0) | |
| argc = sourcefilenames(&argv); | |
| // Make the job list. | |
| njob = 0; | |
| job = emalloc(argc*sizeof job[0]); | |
| for(i=0; i<argc; i++) { | |
| if(suffix(argv[i], "_test.go")) | |
| continue; | |
| job[njob].name = argv[i]; | |
| job[njob].pass = -1; | |
| job[njob].pkg = getpkg(argv[i]); | |
| if(job[njob].pkg && strcmp(job[njob].pkg, "main") == 0) | |
| continue; | |
| njob++; | |
| } | |
| // Look for non-go files, which don't have packages. | |
| // If there's only one package in the go files, use it. | |
| for(i=0; i<njob; i++) { | |
| if(job[i].pkg == nil) { | |
| if(npkg == 1) { | |
| job[i].pkg = pkg[0]; | |
| continue; | |
| } | |
| sysfatal("cannot determine package for %s", job[i].name); | |
| } | |
| } | |
| // TODO: subdirectory packages | |
| // Create empty archives for each package. | |
| for(i=0; i<npkg; i++) { | |
| unlink(smprint("%s.a", pkg[i])); | |
| ar(pkg[i], nil, 0); | |
| } | |
| // Compile by repeated passes: build as many .6 as you can, | |
| // put them in their archives, and repeat. | |
| pending = emalloc(njob*sizeof pending[0]); | |
| for(i=0; i<njob; i++) | |
| pending[i] = &job[i]; | |
| npending = njob; | |
| fail = emalloc(njob*sizeof fail[0]); | |
| success = emalloc(njob*sizeof success[0]); | |
| arfiles = emalloc(njob*sizeof arfiles[0]); | |
| for(pass=0; npending > 0; pass++) { | |
| // Run what we can. | |
| nfail = 0; | |
| nsuccess = 0; | |
| for(i=0; i<npending; i++) { | |
| j = pending[i]; | |
| if(buildcc(compiler(j->name), j->name, 0) < 0) | |
| fail[nfail++] = j; | |
| else{ | |
| if(chatty == 1) | |
| fprint(2, "%s ", j->name); | |
| success[nsuccess++] = j; | |
| } | |
| } | |
| if(nsuccess == 0) { | |
| // Nothing ran; give up. | |
| for(i=0; i<nfail; i++) { | |
| j = fail[i]; | |
| buildcc(compiler(j->name), j->name, 1); | |
| } | |
| exits("stalemate"); | |
| } | |
| if(chatty == 1) | |
| fprint(2, "\n"); | |
| // Update archives. | |
| for(i=0; i<npkg; i++) { | |
| narfiles = 0; | |
| for(k=0; k<nsuccess; k++) { | |
| j = success[k]; | |
| if(j->pkg == pkg[i]) | |
| arfiles[narfiles++] = goobj(j->name, thechar); | |
| j->pass = pass; | |
| } | |
| if(narfiles > 0) | |
| ar(pkg[i], arfiles, narfiles); | |
| for(k=0; k<narfiles; k++) | |
| unlink(arfiles[k]); | |
| } | |
| for(i=0; i<nfail; i++) | |
| pending[i] = fail[i]; | |
| npending = nfail; | |
| } | |
| if(makefile) | |
| writemakefile(); | |
| exits(0); | |
| } |