Commit 7847056d authored by Russ Cox's avatar Russ Cox

rewrite gobuild in go.

R=r
DELTA=1305  (704 added, 590 deleted, 11 changed)
OCL=27546
CL=27548
parent 1cb3b7d1
......@@ -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)
This diff is collapsed.
// 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());
}
}
}
// 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();
}
// 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,
}
// 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;
}
......@@ -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
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment