| // 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; | |
| Dir string; | |
| ObjDir 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] | |
| } | |
| // If current directory is under $GOROOT/src/lib, return the | |
| // path relative to there. Otherwise return "". | |
| func PkgDir() string { | |
| goroot, err := os.Getenv("GOROOT"); | |
| if err != nil || goroot == "" { | |
| return "" | |
| } | |
| srcroot := path.Clean(goroot + "/src/lib/"); | |
| pwd, err1 := os.Getenv("PWD"); // TODO(rsc): real pwd | |
| if err1 != nil || pwd == "" { | |
| return "" | |
| } | |
| if pwd == srcroot { | |
| return "" | |
| } | |
| n := len(srcroot); | |
| if len(pwd) < n || pwd[n] != '/' || pwd[0:n] != srcroot { | |
| return "" | |
| } | |
| dir := pwd[n+1:len(pwd)]; | |
| return dir; | |
| } | |
| 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.Dir = PkgDir(); | |
| z.Char = theChar; // for template | |
| z.ObjDir = ObjDir; // for template | |
| 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.Index(filename, "_test.") >= 0 { | |
| 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); | |
| } | |
| } | |
| } | |
| // Update destination directory. | |
| // If destination directory has same | |
| // name as package name, cut it off. | |
| dir, name := path.Split(z.Dir); | |
| if len(z.Packages) == 1 && z.Packages[0].Name == name { | |
| z.Dir = dir; | |
| } | |
| return z; | |
| } | |
| func PackageObj(pkg string) string { | |
| return pkg + ".a" | |
| } | |
| func (z *Info) Build() { | |
| // Create empty object directory tree. | |
| RemoveAll(ObjDir); | |
| obj := path.Join(ObjDir, z.Dir) + "/"; | |
| MkdirAll(obj); | |
| // Create empty archives. | |
| for pkgname := range z.Pkgmap { | |
| ar := obj + 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(obj + 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() { | |
| RemoveAll(ObjDir); | |
| 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 := 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()); | |
| } | |
| } | |
| } | |