]> Cypherpunks repositories - gostls13.git/commitdiff
rewrite gobuild in go.
authorRuss Cox <rsc@golang.org>
Thu, 16 Apr 2009 07:18:11 +0000 (00:18 -0700)
committerRuss Cox <rsc@golang.org>
Thu, 16 Apr 2009 07:18:11 +0000 (00:18 -0700)
R=r
DELTA=1305  (704 added, 590 deleted, 11 changed)
OCL=27546
CL=27548

src/cmd/gobuild/Makefile
src/cmd/gobuild/gobuild.c [deleted file]
src/cmd/gobuild/gobuild.go [new file with mode: 0644]
src/cmd/gobuild/main.go [new file with mode: 0644]
src/cmd/gobuild/makefile.go [new file with mode: 0644]
src/cmd/gobuild/util.go [new file with mode: 0644]
src/cmd/make.bash
src/make.bash

index 3393990336ca6948573db66e50524ee06399e1f3..28e2a2e0343a389dce8592e40c2e4139a1a9c20d 100644 (file)
@@ -2,19 +2,58 @@
 # Use of this source code is governed by a BSD-style
 # license that can be found in the LICENSE file.
 
-include ../../Make.conf
+# sadly, not auto-generated
 
-TARG=gobuild
-OFILES=\
-       gobuild.$O\
+O=6
+OS=568vq
+GC=$(O)g
+CC=$(O)c -FVw
+AS=$(O)a
+AR=6ar
+LD=$(O)l
 
-$(TARG): $(OFILES)
-       $(LD) -o $(TARG) -L$(GOROOT)/lib $(OFILES) -lbio -l9
+default: gobuild
 
 clean:
-       rm -f $(OFILES) $(TARG)
+       rm -f *.[$(OS)] *.a [$(OS)].out gobuild
 
-install: $(TARG)
-       cp $(TARG) $(BIN)/$(TARG)
+%.$O: %.go
+       $(GC) $*.go
+
+O1=util.$O
+O2=makefile.$O
+O3=gobuild.$O
+
+phases: a1 a2 a3
+gobuild.a: phases
+
+a1: $(O1)
+       $(AR) grc gobuild.a util.$O
+       rm -f $(O1)
+
+a2: $(O2)
+       $(AR) grc gobuild.a makefile.$O
+       rm -f $(O2)
+
+a3: $(O3)
+       $(AR) grc gobuild.a gobuild.$O
+       rm -f $(O3)
+
+newpkg: clean
+       $(AR) grc gobuild.a
+
+$(O1): newpkg
+$(O2): a1
+$(O3): a2
+
+gobuild: main.$O gobuild.a
+       $(LD) -o gobuild main.$O
+
+main.$O: gobuild.a
+
+nuke: clean
+       rm -f $(HOME)/bin/gobuild
+
+install: gobuild
+       cp gobuild $(HOME)/bin/gobuild
 
-$(OFILES): $(HFILES)
diff --git a/src/cmd/gobuild/gobuild.c b/src/cmd/gobuild/gobuild.c
deleted file mode 100644 (file)
index 5368d9f..0000000
+++ /dev/null
@@ -1,590 +0,0 @@
-// 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);
-}
diff --git a/src/cmd/gobuild/gobuild.go b/src/cmd/gobuild/gobuild.go
new file mode 100644 (file)
index 0000000..c28419d
--- /dev/null
@@ -0,0 +1,288 @@
+// 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.
+
+package gobuild
+
+import (
+       "flag";
+       "fmt";
+       "gobuild";
+       "io";
+       "os";
+       "path";
+       "sort";
+       "strings";
+       "template";
+)
+
+type Pkg struct
+
+type File struct {
+       Name string;
+       Pkg *Pkg;
+       Imports []string;
+       Deps []*Pkg;
+       Phase int;
+}
+
+type Pkg struct {
+       Name string;
+       Path string;
+       Files []*File;
+}
+
+type ArCmd struct {
+       Pkg *Pkg;
+       Files []*File;
+}
+
+type Phase struct {
+       Phase int;
+       ArCmds []*ArCmd;
+}
+
+type Info struct {
+       Args []string;
+       Char string;
+       Pkgmap map[string] *Pkg;
+       Packages []*Pkg;
+       Files map[string] *File;
+       Imports map[string] bool;
+       Phases []*Phase;
+       MaxPhase int;
+}
+
+var verbose = flag.Bool("v", false, "verbose mode")
+var writeMakefile = flag.Bool("m", false, "write Makefile to standard output")
+
+func PushPkg(v *[]*Pkg, p *Pkg) {
+       n := len(v);
+       if n >= cap(v) {
+               m := 2*n + 10;
+               a := make([]*Pkg, n, m);
+               for i := range *v {
+                       a[i] = v[i];
+               }
+               *v = a;
+       }
+       *v = v[0:n+1];
+       v[n] = p;
+}
+
+func PushFile(v *[]*File, p *File) {
+       n := len(v);
+       if n >= cap(v) {
+               m := 2*n + 10;
+               a := make([]*File, n, m);
+               for i := range *v {
+                       a[i] = v[i];
+               }
+               *v = a;
+       }
+       *v = v[0:n+1];
+       v[n] = p;
+}
+
+// For sorting Files
+type FileArray []*File
+
+func (a FileArray) Len() int {
+       return len(a)
+}
+
+func (a FileArray) Less(i, j int) bool {
+       return a[i].Name < a[j].Name
+}
+
+func (a FileArray) Swap(i, j int) {
+       a[i], a[j] = a[j], a[i]
+}
+
+func ScanFiles(filenames []string) *Info {
+       // Build list of imports, local packages, and files.
+       // Exclude *_test.go and anything in package main.
+       // TODO(rsc): Build a binary from package main?
+
+       z := new(Info);
+       z.Args = sys.Args;
+       z.Char = theChar;
+       z.Pkgmap = make(map[string] *Pkg);
+       z.Files = make(map[string] *File);
+       z.Imports = make(map[string] bool);
+
+       // Read Go files to find out packages and imports.
+       var pkg *Pkg;
+       for _, filename := range filenames {
+               if strings.HasSuffix(filename, "_test.go") {
+                       continue;
+               }
+               f := new(File);
+               f.Name = filename;
+               if path.Ext(filename) == ".go" {
+                       pkgname, imp, err := PackageImports(filename);
+                       if err != nil {
+                               fatal("parsing", filename, err.String());
+                       }
+                       if pkgname == "main" {
+                               continue;
+                       }
+
+                       path := pkgname;
+                       var ok bool;
+                       pkg, ok = z.Pkgmap[path];
+                       if !ok {
+                               pkg = new(Pkg);
+                               pkg.Name = pkgname;
+                               pkg.Path = path;
+                               z.Pkgmap[path] = pkg;
+                               PushPkg(&z.Packages, pkg);
+                       }
+                       f.Pkg = pkg;
+                       f.Imports = imp;
+                       for _, name := range imp {
+                               z.Imports[name] = true;
+                       }
+                       PushFile(&pkg.Files, f);
+               }
+               z.Files[filename] = f;
+       }
+
+       // Loop through files again, filling in more info.
+       for _, f := range z.Files {
+               if f.Pkg == nil {
+                       // non-Go file: fill in package name.
+                       // Must only be a single package in this directory.
+                       if len(z.Pkgmap) != 1 {
+                               fatal("cannot determine package for ", f.Name);
+                       }
+                       f.Pkg = pkg;
+               }
+
+               // Go file: record dependencies on other packages in this directory.
+               for _, imp := range f.Imports {
+                       pkg, ok := z.Pkgmap[imp];
+                       if ok && pkg != f.Pkg {
+                               PushPkg(&f.Deps, pkg);
+                       }
+               }
+       }
+
+       return z;
+}
+
+func PackageObj(pkg string) string {
+       return pkg + ".a"
+}
+
+func (z *Info) Build() {
+       // Create empty archives.
+       for pkgname := range z.Pkgmap {
+               ar := PackageObj(pkgname);
+               os.Remove(ar);
+               Archive(ar, nil);
+       }
+
+       // Compile by repeated passes: build as many .6 as possible,
+       // put them in their archives, and repeat.
+       var pending, fail, success []*File;
+       for _, file := range z.Files {
+               PushFile(&pending, file);
+       }
+       sort.Sort(FileArray(pending));
+
+       var arfiles []string;
+       z.Phases = make([]*Phase, 0, len(z.Files));
+
+       for phase := 1; len(pending) > 0; phase++ {
+               // Run what we can.
+               fail = fail[0:0];
+               success = success[0:0];
+               for _, f := range pending {
+                       if !Build(Compiler(f.Name), f.Name, false) {
+                               PushFile(&fail, f);
+                       } else {
+                               if *verbose {
+                                       fmt.Fprint(os.Stderr, f.Name, " ");
+                               }
+                               PushFile(&success, f);
+                       }
+               }
+               if len(success) == 0 {
+                       // Nothing ran; give up.
+                       for _, f := range fail {
+                               Build(Compiler(f.Name), f.Name, true);
+                       }
+                       fatal("stalemate");
+               }
+               if *verbose {
+                       fmt.Fprint(os.Stderr, "\n");
+               }
+
+               // Record phase data.
+               p := new(Phase);
+               p.ArCmds = make([]*ArCmd, 0, len(z.Pkgmap));
+               p.Phase = phase;
+               n := len(z.Phases);
+               z.Phases = z.Phases[0:n+1];
+               z.Phases[n] = p;
+
+               // Update archives.
+               for _, pkg := range z.Pkgmap {
+                       arfiles = arfiles[0:0];
+                       var files []*File;
+                       for _, f := range success {
+                               if f.Pkg == pkg {
+                                       PushString(&arfiles, Object(f.Name, theChar));
+                                       PushFile(&files, f);
+                               }
+                               f.Phase = phase;
+                       }
+                       if len(arfiles) > 0 {
+                               Archive(pkg.Name + ".a", arfiles);
+
+                               n := len(p.ArCmds);
+                               p.ArCmds = p.ArCmds[0:n+1];
+                               p.ArCmds[n] = &ArCmd{pkg, files};
+                       }
+                       for _, filename := range arfiles {
+                               os.Remove(filename);
+                       }
+               }
+               pending, fail = fail, pending;
+
+       }
+}
+
+func (z *Info) Clean() {
+       for pkgname := range z.Pkgmap {
+               os.Remove(PackageObj(pkgname));
+       }
+}
+
+func Main() {
+       flag.Parse();
+
+       filenames := flag.Args();
+       if len(filenames) == 0 {
+               var err *os.Error;
+               filenames, err= SourceFiles(".");
+               if err != nil {
+                       fatal("reading .: ", err.String());
+               }
+       }
+
+       state := ScanFiles(filenames);
+       state.Build();
+       if *writeMakefile {
+               t, err, line := template.Parse(makefileTemplate, makefileMap);
+               if err != nil {
+                       fatal("template.Parse: ", err.String());
+               }
+               err = t.Execute(state, os.Stdout);
+               if err != nil {
+                       fatal("template.Expand: ", err.String());
+               }
+       }
+}
+
diff --git a/src/cmd/gobuild/main.go b/src/cmd/gobuild/main.go
new file mode 100644 (file)
index 0000000..da781f9
--- /dev/null
@@ -0,0 +1,14 @@
+// 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.
+
+package main
+
+import (
+       "gobuild";
+)
+
+func main() {
+       gobuild.Main();
+}
+
diff --git a/src/cmd/gobuild/makefile.go b/src/cmd/gobuild/makefile.go
new file mode 100644 (file)
index 0000000..cbdad90
--- /dev/null
@@ -0,0 +1,122 @@
+// 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.
+
+package gobuild
+
+import (
+       "fmt";
+       "gobuild";
+       "io";
+       "path";
+       "template";
+)
+
+var makefileTemplate =
+       "# DO NOT EDIT.  Automatically generated by gobuild.\n"
+       "{Args|args} >Makefile\n"
+       "\n"
+       "O_arm=5\n"     // TODO(rsc): include something here?
+       "O_amd64=6\n"
+       "O_386=8\n"
+       "OS=568vq\n"
+       "\n"
+       "O=$(O_$(GOARCH))\n"
+       "GC=$(O)g\n"
+       "CC=$(O)c -FVw\n"
+       "AS=$(O)a\n"
+       "AR=6ar\n"
+       "\n"
+       "default: packages\n"
+       "\n"
+       "clean:\n"
+       "       rm -f *.[$(OS)] *.a [$(OS)].out\n"
+       "\n"
+       "test: packages\n"
+       "       gotest\n"
+       "\n"
+       "coverage: packages\n"
+       "       gotest\n"
+       "       6cov -g `pwd` | grep -v '_test\\.go:'\n"
+       "\n"
+       "%.$O: %.go\n"
+       "       $(GC) $*.go\n"
+       "\n"
+       "%.$O: %.c\n"
+       "       $(CC) $*.c\n"
+       "\n"
+       "%.$O: %.s\n"
+       "       $(AS) $*.s\n"
+       "\n"
+       "{.repeated section Phases}\n"
+       "O{Phase}=\\\n"
+       "{.repeated section ArCmds}\n"
+       "{.repeated section Files}\n"
+       "       {Name|basename}.$O\\\n"
+       "{.end}\n"
+       "{.end}\n"
+       "\n"
+       "{.end}\n"
+       "\n"
+       "phases:{.repeated section Phases} a{Phase}{.end}\n"
+       "{.repeated section Packages}\n"
+       "{Name}.a: phases\n"
+       "{.end}\n"
+       "\n"
+       "{.repeated section Phases}\n"
+       "a{Phase}: $(O{Phase})\n"
+       "{.repeated section ArCmds}\n"
+       "       $(AR) grc {.section Pkg}{Name}.a{.end}{.repeated section Files} {Name|basename}.$O{.end}\n"
+       "{.end}\n"
+       "       rm -f $(O{Phase})\n"
+       "\n"
+       "{.end}\n"
+       "\n"
+       "newpkg: clean\n"
+       "{.repeated section Packages}\n"
+       "       $(AR) grc {Name}.a\n"
+       "{.end}\n"
+       "\n"
+       "$(O1): newpkg\n"
+       "{.repeated section Phases}\n"
+       "$(O{Phase|+1}): a{Phase}\n"
+       "{.end}\n"
+       "\n"
+       "nuke: clean\n"
+       "       rm -f{.repeated section Packages} $(GOROOT)/pkg/{Name}.a{.end}\n"
+       "\n"
+       "packages:{.repeated section Packages} {Name}.a{.end}\n"
+       "\n"
+       "install: packages\n"
+       "{.repeated section Packages}\n"
+       "       cp {Name}.a $(GOROOT)/pkg/{Name}.a\n"
+       "{.end}\n"
+
+func argsFmt(w io.Write, x interface{}, format string) {
+       args := x.([]string);
+       fmt.Fprint(w, "#");
+       for i, a := range args {
+               fmt.Fprint(w, " ", ShellString(a));
+       }
+}
+
+func basenameFmt(w io.Write, x interface{}, format string) {
+       t := fmt.Sprint(x);
+       t = t[0:len(t)-len(path.Ext(t))];
+       fmt.Fprint(w, MakeString(t));
+}
+
+func plus1Fmt(w io.Write, x interface{}, format string) {
+       fmt.Fprint(w, x.(int) + 1);
+}
+
+func makeFmt(w io.Write, x interface{}, format string) {
+       fmt.Fprint(w, MakeString(fmt.Sprint(x)));
+}
+
+var makefileMap = template.FormatterMap {
+       "": makeFmt,
+       "+1": plus1Fmt,
+       "args": argsFmt,
+       "basename": basenameFmt,
+}
diff --git a/src/cmd/gobuild/util.go b/src/cmd/gobuild/util.go
new file mode 100644 (file)
index 0000000..0224172
--- /dev/null
@@ -0,0 +1,244 @@
+// 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.
+
+
+package gobuild
+
+import (
+       "ast";
+       "exec";
+       "fmt";
+       "os";
+       "parser";
+       "path";
+       "sort";
+       "strconv";
+       "strings";
+)
+
+var (
+       theChar string;
+       goarch string;
+       goos string;
+       bin = make(map[string] string);
+)
+
+var theChars = map[string] string {
+       "amd64": "6",
+       "386": "8",
+       "arm": "5"
+}
+
+func fatal(args ...) {
+       fmt.Fprintf(os.Stderr, "gobuild: %s\n", fmt.Sprint(args));
+       sys.Exit(1);
+}
+
+func init() {
+       var err *os.Error;
+       goarch, err = os.Getenv("GOARCH");
+       goos, err = os.Getenv("GOOS");
+
+       var ok bool;
+       theChar, ok = theChars[goarch];
+       if !ok {
+               fatal("unknown $GOARCH: ", goarch);
+       }
+
+       var binaries = []string{
+               theChar + "g",
+               theChar + "c",
+               theChar + "a",
+               "6ar",  // sic
+       };
+
+       for i, v := range binaries {
+               var s string;
+               if s, err = exec.LookPath(v); err != nil {
+                       fatal("cannot find binary ", v);
+               }
+               bin[v] = s;
+       }
+}
+
+func PushString(v *[]string, p string) {
+       n := len(v);
+       if n >= cap(v) {
+               m := 2*n + 10;
+               a := make([]string, n, m);
+               for i := range *v {
+                       a[i] = v[i];
+               }
+               *v = a;
+       }
+       *v = v[0:n+1];
+       v[n] = p;
+}
+
+
+func run(argv []string, display bool) (ok bool) {
+       argv0 := bin[argv[0]];
+       output := exec.DevNull;
+       if display {
+               output = exec.PassThrough;
+       }
+       p, err1 := exec.Run(argv0, argv, os.Environ(), exec.DevNull, output, output);
+       if err1 != nil {
+               return false;
+       }
+       w, err2 := p.Wait(0);
+       if err2 != nil {
+               return false;
+       }
+       return w.Exited() && w.ExitStatus() == 0;
+}
+
+func Build(cmd []string, file string, display bool) (ok bool) {
+       if display {
+               fmt.Fprint(os.Stderr, "$ ");
+               for i, s := range cmd {
+                       fmt.Fprint(os.Stderr, s[i], " ");
+               }
+               fmt.Fprint(os.Stderr, file, "\n");
+       }
+
+       var argv []string;
+       for i, c := range cmd {
+               PushString(&argv, c);
+       }
+       PushString(&argv, file);
+       return run(argv, display);
+}
+
+func Archive(pkg string, files []string) {
+       argv := []string{ "6ar", "grc", pkg };
+       for i, file := range files {
+               PushString(&argv, file);
+       }
+       if !run(argv, true) {
+               fatal("archive failed");
+       }
+}
+
+func Compiler(file string) []string {
+       switch {
+       case strings.HasSuffix(file, ".go"):
+               return []string{ theChar + "g" };
+       case strings.HasSuffix(file, ".c"):
+               return []string{ theChar + "c", "-FVw" };
+       case strings.HasSuffix(file, ".s"):
+               return []string{ theChar + "a" };
+       }
+       fatal("don't know how to compile ", file);
+       return nil;
+}
+
+func Object(file, suffix string) string {
+       ext := path.Ext(file);
+       return file[0:len(file)-len(ext)] + "." + suffix;
+}
+
+// Dollarstring returns s with literal goarch/goos values
+// replaced by $lGOARCHr where l and r are the specified delimeters.
+func dollarString(s, l, r string) string {
+       out := "";
+       j := 0; // index of last byte in s copied to out.
+       for i := 0; i < len(s); {
+               switch {
+               case i+len(goarch) <= len(s) && s[i:i+len(goarch)] == goarch:
+                       out += s[j:i];
+                       out += "$" + l + "GOARCH" + r;
+                       i += len(goarch);
+                       j = i;
+               case i+len(goos) <= len(s) && s[i:i+len(goos)] == goos:
+                       out += s[j:i];
+                       out += "$" + l + "GOOS" + r;
+                       i += len(goos);
+                       j = i;
+               default:
+                       i++;
+               }
+       }
+       out += s[j:len(s)];
+       return out;
+}
+
+// dollarString wrappers.
+// Print ShellString(s) or MakeString(s) depending on
+// the context in which the result will be interpreted.
+type ShellString string;
+func (s ShellString) String() string {
+       return dollarString(s, "{", "}");
+}
+
+type MakeString string;
+func (s MakeString) String() string {
+       return dollarString(s, "(", ")");
+}
+
+// TODO(rsc): parse.Parse should return an os.Error.
+var ParseError = os.NewError("parse errors");
+
+// TODO(rsc): Should this be in the AST library?
+func LitString(p []*ast.StringLit) (string, *os.Error) {
+       s := "";
+       for i, lit := range p {
+               t, err := strconv.Unquote(string(lit.Value));
+               if err != nil {
+                       return "", err;
+               }
+               s += t;
+       }
+       return s, nil;
+}
+
+func PackageImports(file string) (pkg string, imports []string, err1 *os.Error) {
+       f, err := os.Open(file, os.O_RDONLY, 0);
+       if err != nil {
+               return "", nil, err
+       }
+
+       prog, ok := parser.Parse(f, nil, parser.ImportsOnly);
+       if !ok {
+               return "", nil, ParseError;
+       }
+
+       // Normally one must consult the types of decl and spec,
+       // but we told the parser to return imports only,
+       // so assume it did.
+       var imp []string;
+       for _, decl := range prog.Decls {
+               for _, spec := range decl.(*ast.GenDecl).Specs {
+                       str, err := LitString(spec.(*ast.ImportSpec).Path);
+                       if err != nil {
+                               return "", nil, ParseError;     // ParseError is better than os.EINVAL
+                       }
+                       PushString(&imp, str);
+               }
+       }
+
+       // TODO(rsc): should be prog.Package.Value
+       return prog.Name.Value, imp, nil;
+}
+
+func SourceFiles(dir string) ([]string, *os.Error) {
+       f, err := os.Open(dir, os.O_RDONLY, 0);
+       if err != nil {
+               return nil, err;
+       }
+       names, err1 := f.Readdirnames(-1);
+       f.Close();
+       out := make([]string, 0, len(names));
+       for i, name := range names {
+               if strings.HasSuffix(name, ".go")
+               || strings.HasSuffix(name, ".c")
+               || strings.HasSuffix(name, ".s") {
+                       n := len(out);
+                       out = out[0:n+1];
+                       out[n] = name;
+               }
+       }
+       sort.SortStrings(out);
+       return out, nil;
+}
index 3d812c414bfb48611ac568a41890585bbcb1ce94..08cffbb9b0161b668882fb54e91c697ff49313ac 100644 (file)
@@ -12,7 +12,7 @@ bash mkenam
 make enam.o
 cd ..
 
-for i in cc 6l 6a 6c gc 6g ar db nm acid cov gobuild godefs prof gotest
+for i in cc 6l 6a 6c gc 6g ar db nm acid cov godefs prof gotest
 do
        echo; echo; echo %%%% making $i %%%%; echo
        cd $i
index 830ac70689e1bbfaf85280b8eac5a8bae4f679b5..5c5e36eff0626541f753fc5a74d14143424b776a 100755 (executable)
@@ -18,7 +18,7 @@ rm -f $HOME/bin/quietgcc
 cp quietgcc.bash $HOME/bin/quietgcc
 chmod +x $HOME/bin/quietgcc
 
-for i in lib9 libbio libmach_amd64 libregexp cmd runtime lib
+for i in lib9 libbio libmach_amd64 libregexp cmd runtime lib cmd/gobuild
 do
        echo; echo; echo %%%% making $i %%%%; echo
        cd $i