| // 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 ( |
| "bufio"; |
| "exec"; |
| "fmt"; |
| "io"; |
| "go/ast"; |
| "go/parser"; |
| "os"; |
| "path"; |
| "sort"; |
| "strconv"; |
| "strings"; |
| ) |
| |
| const ( |
| ShowErrors = 1<<iota; |
| ForceDisplay; |
| ) |
| |
| var ( |
| theChar string; |
| goarch string; |
| goos string; |
| bin = make(map[string] string); |
| ) |
| |
| var theChars = map[string] string { |
| "amd64": "6", |
| "386": "8", |
| "arm": "5" |
| } |
| |
| const ObjDir = "_obj" |
| |
| func fatal(format string, args ...) { |
| fmt.Fprintf(os.Stderr, "gobuild: %s\n", fmt.Sprintf(format, args)); |
| os.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: %s", goarch); |
| } |
| |
| var binaries = []string{ |
| theChar + "g", |
| theChar + "c", |
| theChar + "a", |
| "gopack", |
| }; |
| |
| for i, v := range binaries { |
| var s string; |
| if s, err = exec.LookPath(v); err != nil { |
| fatal("cannot find binary %s", v); |
| } |
| bin[v] = s; |
| } |
| } |
| |
| func PushString(vp *[]string, p string) { |
| v := *vp; |
| 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; |
| *vp = v; |
| } |
| |
| func run(argv []string, flag int) (ok bool) { |
| argv0 := bin[argv[0]]; |
| null, err := os.Open("/dev/null", os.O_RDWR, 0); |
| if err != nil { |
| fatal("open /dev/null: %s", err); |
| } |
| defer null.Close(); |
| r, w, err := os.Pipe(); |
| if err != nil { |
| fatal("pipe: %s", err); |
| } |
| pid, err := os.ForkExec(argv0, argv, os.Environ(), "", []*os.File{null, w, w}); |
| defer r.Close(); |
| w.Close(); |
| if err != nil { |
| return false; |
| } |
| |
| // Read the first line of output, if any. Discard the rest. |
| // If there is output and ShowErrors is set, show it, |
| // preceded by a shell command line. |
| // If ForceDisplay is set, we show the command even |
| // if there's no output; this gets set if we're just trying |
| // to keep the user informed. |
| b := bufio.NewReader(r); |
| line, err := b.ReadLineString('\n', true); |
| if flag & ShowErrors != 0 && line != "" || flag & ForceDisplay != 0 { |
| fmt.Fprint(os.Stderr, "$ "); |
| for i, s := range argv { |
| fmt.Fprint(os.Stderr, s, " "); |
| } |
| fmt.Fprint(os.Stderr, "\n"); |
| fmt.Fprint(os.Stderr, " ", line); |
| io.Copy(r, null); // don't let process block on pipe |
| } |
| waitmsg, err := os.Wait(pid, 0); |
| if err != nil { |
| return false; |
| } |
| return waitmsg.Exited() && waitmsg.ExitStatus() == 0; |
| } |
| |
| func Build(cmd []string, file string, flag int) (ok bool) { |
| var argv []string; |
| for i, c := range cmd { |
| PushString(&argv, c); |
| } |
| PushString(&argv, file); |
| return run(argv, flag); |
| } |
| |
| func Archive(pkg string, files []string) { |
| argv := []string{ "gopack", "grc", pkg }; |
| for i, file := range files { |
| PushString(&argv, file); |
| } |
| if !run(argv, ShowErrors) { |
| fatal("archive failed"); |
| } |
| } |
| |
| func Compiler(file string) []string { |
| switch { |
| case strings.HasSuffix(file, ".go"): |
| return []string{ theChar + "g", "-I", ObjDir }; |
| 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 %s", 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(string(s), "{", "}"); |
| } |
| |
| type MakeString string; |
| func (s MakeString) String() string { |
| return dollarString(string(s), "(", ")"); |
| } |
| |
| // 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, err := parser.Parse(f, parser.ImportsOnly); |
| if err != nil { |
| return "", nil, err; |
| } |
| |
| // 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, os.NewError("invalid import specifier"); // 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; |
| } |
| |
| // TODO(rsc): Implement these for real as |
| // os.MkdirAll and os.RemoveAll and then |
| // make these wrappers that call fatal on error. |
| |
| func MkdirAll(name string) { |
| p, err := exec.Run("/bin/mkdir", []string{"mkdir", "-p", name}, os.Environ(), exec.DevNull, exec.PassThrough, exec.PassThrough); |
| if err != nil { |
| fatal("run /bin/mkdir: %v", err); |
| } |
| w, err1 := p.Wait(0); |
| if err1 != nil { |
| fatal("wait /bin/mkdir: %v", err); |
| } |
| if !w.Exited() || w.ExitStatus() != 0 { |
| fatal("/bin/mkdir: %v", w); |
| } |
| } |
| |
| func RemoveAll(name string) { |
| p, err := exec.Run("/bin/rm", []string{"rm", "-rf", name}, os.Environ(), exec.DevNull, exec.PassThrough, exec.PassThrough); |
| if err != nil { |
| fatal("run /bin/rm: %v", err); |
| } |
| w, err1 := p.Wait(0); |
| if err1 != nil { |
| fatal("wait /bin/rm: %v", err); |
| } |
| if !w.Exited() || w.ExitStatus() != 0 { |
| fatal("/bin/rm: %v", w); |
| } |
| |
| } |
| |