Index: tool/gen.go |
diff --git a/tool/gen.go b/tool/gen.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d5b761a2a1e0c20dac8664b8a05e14f5314aab81 |
--- /dev/null |
+++ b/tool/gen.go |
@@ -0,0 +1,558 @@ |
+// Copyright 2011 Jose Luis Vázquez González josvazg@gmail.com |
+// Use of this source code is governed by a BSD-style |
+ |
+package tool |
+ |
+import ( |
+ "bytes" |
+ "exec" |
+ "fmt" |
+ "github.com/josvazg/remotize" |
+ "io" |
+ "os" |
+ "reflect" |
+ "sort" |
+ "strconv" |
+ "strings" |
+) |
+ |
+const remotizePkg = "github.com/josvazg/remotize" |
+ |
+// remotizer code head and tail & marker |
+const ( |
+ remotizerHead = `// Autogenerated Remotizer [DO NOT EDIT!] |
+package main |
+ |
+` |
+ remotizerTail = `func main() { |
+ for _,spec := range toremotize { |
+ if e:=tool.Remotize(spec); e!=nil { |
+ panic(e) |
+ } |
+ } |
+}` |
+) |
+ |
+// The General Spec for a Remotization |
+type Spec struct { |
+ packname string |
+ name string |
+ isInterface bool |
+ t reflect.Type |
+ imports map[string]string |
+} |
+ |
+// NewSpec will create an Spec to be remotized. |
+func NewSpec(pack string, isInterface bool, i interface{}) *Spec { |
+ t := reflect.TypeOf(i) |
+ bt := baseType(t) |
+ return &Spec{pack, bt.Name(), isInterface, bt, make(map[string]string)} |
+} |
+ |
+// Value2Spec turns a sample value into a remotization Spec for that kind of value. |
+func Value2Spec(pack string, i interface{}) *Spec { |
+ return Type2Spec(pack, reflect.TypeOf(i)) |
+} |
+ |
+// Type2Spec turns a type into a Spec to be remotized. |
+func Type2Spec(pack string, t reflect.Type) *Spec { |
+ bt := baseType(t) |
+ isInterface := (bt.Kind() == reflect.Interface) |
+ if t.NumMethod() == 0 { |
+ t = bt |
+ } |
+ return &Spec{pack, ifacename(bt.Name()), isInterface, t, make(map[string]string)} |
+} |
+ |
+// Remotize remotizes a type, interface or source code specified in a Spec by generating |
+// the correct wrapper for that type. |
+func Remotize(spec *Spec) os.Error { |
+ if spec.name == "" { |
+ return os.NewError(fmt.Sprintf("Can't remotize unnamed interface from ", spec)) |
+ } |
+ def := spec.buildInterfaceDef() |
+ hdr := spec.buildHeader() |
+ body := spec.buildBody() |
+ if spec.isInterface { |
+ def = "" |
+ } |
+ source := fmt.Sprintf("%s%s%s", hdr, def, body) |
+ return gofmtSave("remotized"+spec.name, source) |
+} |
+ |
+// nameOf returns the base NON pointer type referred by t |
+func baseType(t reflect.Type) reflect.Type { |
+ if t.Kind() == reflect.Ptr { |
+ return baseType(t.Elem()) |
+ } |
+ return t |
+} |
+ |
+// buildInterfaceDef builds the interface definition and fills the imports field |
+func (s *Spec) buildInterfaceDef() string { |
+ def := bytes.NewBufferString("") |
+ ifacename := s.name |
+ if !s.isInterface { |
+ ifacename += remotize.Suffix(s.name) |
+ } |
+ fmt.Fprintf(def, "type %s interface", ifacename) |
+ if s.t.NumMethod() > 0 { |
+ fmt.Fprintf(def, " {") |
+ for i := 0; i < s.t.NumMethod(); i++ { |
+ m := s.t.Method(i) |
+ if isExported(m.Name) { |
+ fmt.Fprintf(def, "\n ") |
+ s.funcsource(def, s.t, &m) |
+ } |
+ } |
+ fmt.Fprintf(def, "\n}\n\n") |
+ } |
+ return def.String() |
+} |
+ |
+// funcsource will generate the source code for a function declaration (no body) |
+func (s *Spec) funcsource(w io.Writer, t reflect.Type, m *reflect.Method) { |
+ start := 0 |
+ if t.Kind() == reflect.Interface { |
+ fmt.Fprintf(w, m.Name+"(") |
+ } else if m != nil && m.Name != "" { |
+ start++ |
+ fmt.Fprintf(w, m.Name+"(") |
+ } else { |
+ fmt.Fprintf(w, "func (") |
+ } |
+ if m != nil { |
+ t = m.Type |
+ } |
+ for i := start; i < t.NumIn(); i++ { |
+ s.typesource(w, t.In(i)) |
+ if (i + 1) != t.NumIn() { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ fmt.Fprintf(w, ") ") |
+ if t.NumOut() > 1 { |
+ fmt.Fprintf(w, "(") |
+ } |
+ for i := 0; i < t.NumOut(); i++ { |
+ s.typesource(w, t.Out(i)) |
+ if (i + 1) != t.NumOut() { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ if t.NumOut() > 1 { |
+ fmt.Fprintf(w, ")") |
+ } |
+} |
+ |
+// typesource will generate the source code for a type |
+func (s *Spec) typesource(w io.Writer, t reflect.Type) { |
+ switch t.Kind() { |
+ case reflect.Array: |
+ fmt.Fprintf(w, "["+strconv.Itoa(t.Len())+"]") |
+ s.typesource(w, t.Elem()) |
+ case reflect.Chan: |
+ fmt.Fprintf(w, "chan ") |
+ s.typesource(w, t.Elem()) |
+ case reflect.Func: |
+ s.funcsource(w, t, nil) |
+ case reflect.Map: |
+ fmt.Fprintf(w, "map[") |
+ s.typesource(w, t.Key()) |
+ fmt.Fprintf(w, "]") |
+ s.typesource(w, t.Elem()) |
+ case reflect.Ptr: |
+ fmt.Fprintf(w, "*") |
+ s.typesource(w, t.Elem()) |
+ case reflect.Slice: |
+ fmt.Fprintf(w, "[]") |
+ s.typesource(w, t.Elem()) |
+ case reflect.String: |
+ fmt.Fprintf(w, "string") |
+ default: |
+ s.pack(t) |
+ name := t.String() |
+ if t.PkgPath() == s.packname { |
+ name = t.Name() |
+ } |
+ fmt.Fprintf(w, name) |
+ return |
+ } |
+} |
+ |
+// pack will record the type's package as an import |
+func (s *Spec) pack(t reflect.Type) { |
+ packpath := t.PkgPath() |
+ if packpath != "" { |
+ alias := path2pack(packpath) |
+ s.imports[alias] = packpath |
+ } |
+} |
+ |
+// buildHeader will build the header for the source file including package and imports |
+func (s *Spec) buildHeader() string { |
+ hdr := bytes.NewBufferString("// Autogenerated by josvazg/remotize/tool - no need to edit!\n") |
+ fmt.Fprintf(hdr, "package %v\n\n", path2pack(s.packname)) |
+ if s.imports == nil { |
+ s.imports = make(map[string]string) |
+ } |
+ s.imports["rpc"] = "rpc" |
+ s.imports["os"] = "os" |
+ typepack := baseType(s.t).PkgPath() |
+ if s.packname != typepack && typepack != "main" && s.t.Kind() == reflect.Interface { |
+ s.imports[typepack] = typepack |
+ } |
+ if s.packname != remotizePkg { |
+ s.imports[remotizePkg] = remotizePkg |
+ } |
+ names := make([]string, 0) |
+ for _, name := range s.imports { |
+ names = append(names, name) |
+ } |
+ writeImports(hdr, names, s.imports, s.packname) |
+ return hdr.String() |
+} |
+ |
+// buildBody builds the wrapper body source code |
+func (s *Spec) buildBody() string { |
+ src := bytes.NewBufferString("") |
+ fmt.Fprintf(src, "// Autoregistry\n") |
+ fmt.Fprintf(src, "func init() {\n") |
+ fmt.Fprintf(src, " remotize.Register(Remote%s{},\n", s.name) |
+ fmt.Fprintf(src, " func(cli *rpc.Client) interface{} "+ |
+ "{\n\t\t\treturn NewRemote%s(cli)\n\t\t},\n", s.name) |
+ fmt.Fprintf(src, " %sService{},\n", s.name) |
+ fmt.Fprintf(src, " func(i interface{}) interface{} {") |
+ fmt.Fprintf(src, "\n\t\t\treturn New%sService(i.(%s))\n\t\t},\n", s.name, s.fullname()) |
+ fmt.Fprintf(src, " )\n") |
+ fmt.Fprintf(src, "}\n\n") |
+ s.remoteInit(src) |
+ s.localInit(src) |
+ for i := 0; i < s.t.NumMethod(); i++ { |
+ m := s.t.Method(i) |
+ if isExported(m.Name) { |
+ s.wrapMethod(src, m) |
+ } |
+ } |
+ return src.String() |
+} |
+ |
+// fullname return the appropiate full name with or without package prefix for the remotized type |
+func (s *Spec) fullname() string { |
+ if !s.isInterface || s.t.PkgPath() == s.packname || s.t.PkgPath() == "main" { |
+ return s.name |
+ } |
+ return s.t.String() |
+} |
+ |
+// remoteInit prepares the service header |
+func (s *Spec) remoteInit(w io.Writer) { |
+ fmt.Fprintf(w, "// Rpc service wrapper for %s\n", s.name) |
+ fmt.Fprintf(w, "type %sService struct {\n", s.name) |
+ fmt.Fprintf(w, " srv %s\n", s.fullname()) |
+ fmt.Fprintf(w, "}\n\n") |
+ fmt.Fprintf(w, "// Direct %sService constructor\n", s.name) |
+ fmt.Fprintf(w, "func New%sService(impl %s) *%sService {\n", s.name, s.fullname(), s.name) |
+ fmt.Fprintf(w, " return &%sService{impl}\n", s.name) |
+ fmt.Fprintf(w, "}\n\n") |
+} |
+ |
+// localInit prepares the client header |
+func (s *Spec) localInit(w io.Writer) { |
+ fmt.Fprintf(w, "// Rpc client for %s\n", s.name) |
+ fmt.Fprintf(w, "type Remote%s struct {\n", s.name) |
+ fmt.Fprintf(w, " cli *rpc.Client\n") |
+ fmt.Fprintf(w, "}\n\n") |
+ fmt.Fprintf(w, "// Direct Remote%s constructor\n", s.name) |
+ fmt.Fprintf(w, "func NewRemote%s(cli *rpc.Client) *Remote%s {\n", s.name, s.name) |
+ fmt.Fprintf(w, " return &Remote%s{cli}\n", s.name) |
+ fmt.Fprintf(w, "}\n\n") |
+} |
+ |
+// wrapMethod generates the wrappers for one method |
+func (s *Spec) wrapMethod(w io.Writer, m reflect.Method) { |
+ fmt.Fprintf(w, "// wrapper for: %s\n\n", m.Name) |
+ args := make([]reflect.Type, 0) |
+ start := 0 |
+ if s.t.Kind() != reflect.Interface { // avoid the first receiver arg on non interfaces |
+ start = 1 |
+ } |
+ for i := start; i < m.Type.NumIn(); i++ { |
+ args = append(args, m.Type.In(i)) |
+ } |
+ s.generateStructWrapper(w, args, "Args", m.Name) |
+ results, inouts := prepareInOuts(m.Type, start) |
+ s.generateStructWrapper(w, results, "Reply", m.Name) |
+ s.generateServerRPCWrapper(w, m, inouts, start) |
+ s.generateClientRPCWrapper(w, m, inouts, start) |
+ fmt.Fprintf(w, "\n") |
+} |
+ |
+// generateStructWrapper generates a argument or result struct |
+func (s *Spec) generateStructWrapper(w io.Writer, pars []reflect.Type, structname, name string) { |
+ fmt.Fprintf(w, "type %s%s%s struct {\n", s.name, name, structname) |
+ for i, par := range pars { |
+ fmt.Fprintf(w, "\tArg%d ", i) |
+ s.typesource(w, par) |
+ fmt.Fprintf(w, "\n") |
+ } |
+ fmt.Fprintf(w, "}\n\n") |
+} |
+ |
+// prepareInOuts detects how many pointers in the args must be returned as results (in & outs) |
+func prepareInOuts(ft reflect.Type, start int) ([]reflect.Type, []int) { |
+ results := make([]reflect.Type, 0) |
+ inouts := make([]int, 0) |
+ for i := start; i < ft.NumIn(); i++ { |
+ if ft.In(i).Kind() == reflect.Ptr { |
+ results = append(results, ft.In(i)) |
+ inouts = append(inouts, i) |
+ } |
+ } |
+ for i := 0; i < ft.NumOut(); i++ { |
+ results = append(results, ft.Out(i)) |
+ } |
+ return results, inouts |
+} |
+ |
+// function that is exposed to an RPC API, but calls simple "Server_" one |
+func (s *Spec) generateServerRPCWrapper(w io.Writer, m reflect.Method, inouts []int, start int) { |
+ name := m.Name |
+ ins := m.Type.NumIn() |
+ outs := m.Type.NumOut() |
+ fmt.Fprintf(w, "func (r *%sService) %s(args *%s%sArgs, "+ |
+ "reply *%s%sReply) os.Error {\n", s.name, name, s.name, name, s.name, name) |
+ fmt.Fprintf(w, "\t") |
+ for i := 0; i < outs; i++ { |
+ fmt.Fprintf(w, "reply.Arg%d", i) |
+ if i != outs-1 { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ if outs > 0 { |
+ fmt.Fprintf(w, " = ") |
+ } |
+ fmt.Fprintf(w, "r.srv.%s(", name) |
+ for i := start; i < ins; i++ { |
+ fmt.Fprintf(w, "args.Arg%d", i-start) |
+ if i != ins-1 { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ fmt.Fprintf(w, ")\n") |
+ for i := outs; i < len(inouts); i++ { |
+ fmt.Fprintf(w, "\treply.Arg%d=args.Arg%d\n", i, inouts[i-outs]) |
+ } |
+ fmt.Fprintf(w, "\treturn nil\n}\n\n") |
+} |
+ |
+// generateClientRPCWrapper generates the client side wrapper |
+func (s *Spec) generateClientRPCWrapper(w io.Writer, m reflect.Method, inouts []int, start int) { |
+ name := m.Name |
+ ins := m.Type.NumIn() |
+ outs := m.Type.NumOut() |
+ fmt.Fprintf(w, "func (l *Remote%s) %s(", s.name, name) |
+ s.printFuncFieldListUsingArgs(w, m.Type, start) |
+ fmt.Fprintf(w, ") ") |
+ s.printFuncResultList(w, m.Type) |
+ fmt.Fprintf(w, "{\n") |
+ fmt.Fprintf(w, "\tvar args %s%sArgs\n", s.name, name) |
+ fmt.Fprintf(w, "\tvar reply %s%sReply\n", s.name, name) |
+ for i := start; i < ins; i++ { |
+ fmt.Fprintf(w, "\targs.Arg%d = Arg%d\n", i-start, i-start) |
+ } |
+ fmt.Fprintf(w, "\terr := l.cli.Call(\"%sService.%s\", &args, &reply)\n", s.name, name) |
+ fmt.Fprintf(w, "\tif err != nil {\n") |
+ fmt.Fprintf(w, "\t\tpanic(err.String())\n\t}\n") |
+ for i := outs; i < len(inouts); i++ { |
+ fmt.Fprintf(w, "\t*Arg%d=*reply.Arg%d\n", i, inouts[i-outs]) |
+ } |
+ fmt.Fprintf(w, "\treturn ") |
+ for i := 0; i < outs; i++ { |
+ fmt.Fprintf(w, "reply.Arg%d", i) |
+ if i != outs-1 { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ fmt.Fprintf(w, "\n}\n\n") |
+} |
+ |
+// printFuncFieldListUsingArgs generates the func field list with argX names |
+func (s *Spec) printFuncFieldListUsingArgs(w io.Writer, t reflect.Type, start int) { |
+ if t.IsVariadic() { |
+ panic(fmt.Sprintf("Variadic argument lists as in '%v' are not supported!", t)) |
+ } |
+ for i := start; i < t.NumIn(); i++ { |
+ // names |
+ fmt.Fprintf(w, "Arg%d ", i-start) |
+ s.typesource(w, t.In(i)) |
+ // , |
+ if i != t.NumIn()-1 { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+} |
+ |
+// printFuncResultList generates the func field list with type names |
+func (s *Spec) printFuncResultList(w io.Writer, t reflect.Type) { |
+ outs := t.NumOut() |
+ if outs > 1 { |
+ fmt.Fprintf(w, "(") |
+ } |
+ for i := 0; i < outs; i++ { |
+ // names |
+ s.typesource(w, t.Out(i)) |
+ // , |
+ if i != outs-1 { |
+ fmt.Fprintf(w, ", ") |
+ } |
+ } |
+ if outs > 1 { |
+ fmt.Fprintf(w, ") ") |
+ } else { |
+ fmt.Fprintf(w, " ") |
+ } |
+} |
+ |
+// generateRemotizerCode returns the remotizer source code for a given set of Detected remotizables |
+func generateRemotizerCode(pspecs []PreSpec) string { |
+ src := bytes.NewBuffer(make([]byte, 0)) |
+ fmt.Fprintf(src, remotizerHead) |
+ genImports(src, pspecs) |
+ genTypeDefs(src, pspecs) |
+ fmt.Fprintf(src, "var toremotize = []*tool.Spec{") |
+ for _, ps := range pspecs { |
+ // complete types with methods... |
+ if dcl,ok:=ps.(*decl); ok { |
+ fmt.Fprintf(src, "\n\ttool.NewSpec(\"%v\",", ps.packname()) |
+ fmt.Fprintf(src, "%v,", dcl.isInterface) |
+ fmt.Fprintf(src, "new(%v)),", ifacename(ps.name())) |
+ } |
+ // ... or just a type name predefined elsewhere |
+ if _,ok:=ps.(*predefined); ok { |
+ fmt.Fprintf(src, "\n\ttool.Value2Spec(\"%v\",new(%v)),", ps.packname(), ps.name()) |
+ } |
+ } |
+ fmt.Fprintf(src, "\n}\n\n") |
+ fmt.Fprintf(src, remotizerTail) |
+ return src.String() |
+} |
+ |
+// genImports adds imports to the remotizer source code from types |
+func genImports(src io.Writer, pspecs []PreSpec) { |
+ imports := []string{"tool"} |
+ aliases:=make(map[string]string) |
+ aliases["tool"] = "github.com/josvazg/remotize/tool" |
+ for _, pspec:=range pspecs { |
+ if decl,ok:=pspec.(*decl); ok { |
+ for alias,name:=range decl.detected.aliases { |
+ aliases[alias]=name |
+ } |
+ for packname, _ := range decl.imports { |
+ imports = addImport(imports, packname) |
+ } |
+ } else if _,ok:=pspec.(*predefined); ok { |
+ packname := strings.SplitN(pspec.name(), ".", 2)[0] |
+ imports = addImport(imports, packname) |
+ } |
+ } |
+ writeImports(src, imports, aliases, "") |
+} |
+ |
+// addImport will add a packname import into a list making sure it's nor repeated |
+func addImport(imports []string, packname string) []string { |
+ for _, imp := range imports { |
+ if packname == imp { |
+ return imports |
+ } |
+ } |
+ return append(imports, packname) |
+} |
+ |
+// genTypeDefs adds types and interface source code definitions from this packages to the remotizer |
+func genTypeDefs(src io.Writer, pspecs []PreSpec) { |
+ for _, pspec := range pspecs { |
+ if decl,ok:=pspec.(*decl); ok && decl.Src != nil { |
+ fmt.Fprintf(src, "\n%s\n", decl.Src) |
+ } |
+ } |
+} |
+ |
+// writeImports will generate an import set source code |
+func writeImports(w io.Writer, imports []string, aliases map[string]string, skip string) { |
+ sort.Strings(imports) |
+ fmt.Fprintf(w, "import (\n") |
+ for _, s := range imports { |
+ v := aliases[s] |
+ if v == skip || v == "" { |
+ continue |
+ } |
+ if s == v || strings.HasSuffix(v, "/"+s) { |
+ fmt.Fprintf(w, "\t\"%s\"\n", v) |
+ } else { |
+ fmt.Fprintf(w, "\t%s \"%s\"\n", s, v) |
+ } |
+ } |
+ fmt.Fprintf(w, ")\n\n") |
+} |
+ |
+// BuildRemotizer generates a program to remotize the detected interfaces. |
+func BuildRemotizer(pspecs []PreSpec) os.Error { |
+ src := generateRemotizerCode(pspecs) |
+ filename := "_remotizer" |
+ if e := gofmtSave(filename, src); e != nil { |
+ return e |
+ } |
+ if o, e := runCmd(gocompile(), "-I", "_test", filename+".go"); e != nil { |
+ fmt.Fprintf(os.Stderr, string(o)+"\n") |
+ return e |
+ } |
+ if o, e := runCmd(golink(), "-L", "_test", "-o", filename, |
+ filename+"."+goext()); e != nil { |
+ fmt.Fprintf(os.Stderr, string(o)+"\n") |
+ return e |
+ } |
+ if o, e := runCmd("./" + filename); e != nil { |
+ fmt.Fprintf(os.Stderr, string(o)+"\n") |
+ return e |
+ } |
+ return nil |
+} |
+ |
+// runs a command |
+func runCmd(cmdargs ...string) ([]byte, os.Error) { |
+ fmt.Println(cmdargs) |
+ return exec.Command(cmdargs[0], cmdargs[1:]...).CombinedOutput() |
+} |
+ |
+// dictionary cache |
+var dict map[string]string |
+ |
+// go tool execution string |
+func goexec(tool string) string { |
+ if dict == nil { |
+ dict = make(map[string]string) |
+ dict["386"] = "8" |
+ dict["amd64"] = "6" |
+ dict["arm"] = "5" |
+ dict["compiler"] = "g" |
+ dict["linker"] = "l" |
+ } |
+ return dict[os.Getenv("GOARCH")] + dict[tool] |
+} |
+ |
+// Go compiler |
+func gocompile() string { |
+ return goexec("compiler") |
+} |
+ |
+// Go linker |
+func golink() string { |
+ return goexec("linker") |
+} |
+ |
+// Go architecture extension |
+func goext() string { |
+ return goexec("") |
+} |
+ |