diff --git a/tools/generator.go b/tools/generator.go new file mode 100644 index 0000000..02ca954 --- /dev/null +++ b/tools/generator.go @@ -0,0 +1,132 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" +) + +type visitor func(node ast.Node) bool + +func (v visitor) Visit(node ast.Node) ast.Visitor { + if v(node) { + return v + } + + return nil +} + +// Generator stores progress of package parser. +type Generator struct { + parsedPkgs map[string]bool + pkgs []string + context *Context +} + +func (g *Generator) Parse() { + for i := 0; i < len(g.pkgs); i++ { + g.parsePkg(g.pkgs[i]) + } +} + +func (g *Generator) parsePkg(pkg string) { + pkgPath := filepath.Join(g.context.GoPackage, pkg) + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, pkgPath, func(info os.FileInfo) bool { + // Filter out all test files. + if strings.HasSuffix(info.Name(), "_test.go") { + return false + } + + return true + }, parser.ParseComments) + + if err != nil { + panic(err) + } + + if _, ok := pkgs["main"]; ok { + delete(pkgs, "main") + } + + if len(pkgs) != 1 { + keys := []string{} + + for k, _ := range pkgs { + keys = append(keys, k) + } + + panic(fmt.Errorf("there must be only one package name in a package. [pkgs:%v]", strings.Join(keys, ", "))) + } + + //goDir := "go" + g.context.Version.Join("_") + //output := filepath.Join(g.context.Output, goDir, pkg) + //importPath := filepath.Join(g.context.ImportPath, goDir, pkg) + + for _, p := range pkgs { + //name := p.Name + files := p.Files + + for _, f := range files { + decls := f.Decls + neededDecls := []ast.Decl{} + + for _, decl := range decls { + // Only type decl is needed. + if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { + neededDecls = append(neededDecls, decl) + specs := genDecl.Specs + + for _, spec := range specs { + ast.Walk(visitor(func(node ast.Node) bool { + if node == nil { + return false + } + + switch n := node.(type) { + case *ast.TypeSpec: + typeName := n.Name.Name + logTracef("Find type. [type:%v]", typeName) + + case *ast.SelectorExpr: + pkgName := n.X.(*ast.Ident).Name + typeName := n.Sel.Name + + if pkgName == "C" { + break + } + + logTracef("Find type. [package-name:%v] [type:%v]", pkgName, typeName) + } + + return true + }), spec) + } + } + } + + f.Decls = neededDecls + } + } +} + +// Generate hacked files for packages. +// Basically, it extracts all types and generates hacked go files. +// +// Panic if it encounters any error. +func GenerateHackedFiles(context *Context, pkgs ...string) { + generator := &Generator{ + parsedPkgs: make(map[string]bool), + pkgs: pkgs, + context: context, + } + generator.Parse() +} diff --git a/tools/log.go b/tools/log.go new file mode 100644 index 0000000..656a345 --- /dev/null +++ b/tools/log.go @@ -0,0 +1,23 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" +) + +func logFatalf(format string, args ...interface{}) { + err := fmt.Errorf(format, args...) + panic(err) +} + +func logErrorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "ERROR: "+format+"\n", args...) +} + +func logTracef(format string, args ...interface{}) { + fmt.Printf("TRACE: "+format+"\n", args...) +} diff --git a/tools/main.go b/tools/main.go new file mode 100644 index 0000000..5803c90 --- /dev/null +++ b/tools/main.go @@ -0,0 +1,153 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "regexp" +) + +var ( + flagGoSrc string + flagOutput string + flagImportPath string + + reImportPath = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z0-9_]+)*(/[a-zA-Z0-9_]+)*$`) +) + +type Context struct { + GoSrc string // Path to go src root directory. + GoPackage string // Path to package src in go src directory. + Version Version // Version number. + Output string // Path to output directory. + ImportPath string // The prefix of import path for output directory. +} + +func init() { + flag.StringVar(&flagGoSrc, "go-src", "", "Path to go src directory.") + flag.StringVar(&flagOutput, "output", "", "Output path. Default is current directory.") + flag.StringVar(&flagImportPath, "import-path", "", "The prefix of import path for output directory.") +} + +func validateGoSrc(context *Context) { + if flagGoSrc == "" { + logFatalf("Flag -go-src must be set.") + } + + src, err := filepath.Abs(flagGoSrc) + + if err != nil { + logFatalf("Fail to get absolute path of go src. [go-src:%v]", flagGoSrc) + } + + info, err := os.Stat(src) + + if err != nil { + logFatalf("Value of flag -go-src must be a valid directory name. [go-src:%v]", flagGoSrc) + } + + if !info.Mode().IsDir() { + logFatalf("Value of flag -go-src must be a directory. [go-src:%v]", flagGoSrc) + } + + srcsrc := filepath.Join(src, "src") + info, err = os.Stat(srcsrc) + + if err != nil { + logFatalf("Fail to find `src` in go src directory. [go-src:%v]", flagGoSrc) + } + + if !info.Mode().IsDir() { + logFatalf("The `src` in go src must be a directory. [go-src:%v]", flagGoSrc) + } + + versionPath := filepath.Join(src, "VERSION") + versionFile, err := os.Open(versionPath) + + if err != nil { + logFatalf("Fail to read VERSION in go src directory. Are you sure -go-src points to a valid go src directory? [go-src:%v]", flagGoSrc) + } + + defer versionFile.Close() + versionBuf := &bytes.Buffer{} + versionBuf.ReadFrom(versionFile) + + if versionBuf.Len() == 0 { + logFatalf("VERSION in go src is empty. [go-src:%v]", flagGoSrc) + } + + version, err := ParseVersion(versionBuf.String()) + + if err != nil { + logFatalf("VERSION file content is not valid. [go-src:%v] [err:%v] [content:%v]", flagGoSrc, err, versionBuf.String()) + } + + context.GoSrc = src + context.GoPackage = srcsrc + context.Version = version +} + +func validateOutput(context *Context) { + var err error + + if flagOutput == "" { + flagOutput, err = os.Getwd() + + if err != nil { + logFatalf("Value of flag -output is empty and current directory is not available.") + } + } + + output, err := filepath.Abs(flagOutput) + + if err != nil { + logFatalf("Fail to get absolute path of output. [output:%v]", flagOutput) + } + + info, err := os.Stat(output) + + if err != nil { + logFatalf("Value of flag -output must be a valid directory name. [output:%v]", flagOutput) + } + + if !info.Mode().IsDir() { + logFatalf("Value of flag -output must be a directory. [output:%v]", flagOutput) + } + + context.Output = output +} + +func validateImportPath(context *Context) { + if flagImportPath == "" { + logFatalf("Flag -import-path must be set.") + } + + if !reImportPath.MatchString(flagImportPath) { + logFatalf("Value of flag -import-path must be a valid import path. [import-path:%v]", flagImportPath) + } + + context.ImportPath = flagImportPath +} + +func main() { + defer func() { + if err := recover(); err != nil { + logErrorf("%v", err) + os.Exit(1) + } + }() + + flag.Parse() + + context := &Context{} + validateGoSrc(context) + validateOutput(context) + validateImportPath(context) + + GenerateHackedFiles(context, "runtime") +} diff --git a/tools/version.go b/tools/version.go new file mode 100644 index 0000000..5319034 --- /dev/null +++ b/tools/version.go @@ -0,0 +1,73 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "regexp" + "strings" +) + +var reVersion = regexp.MustCompile(`^go(\d+)(\.(\d+))+`) + +type Version []string + +// Parse version string like "go1.6.3". +func ParseVersion(version string) (Version, error) { + matches := reVersion.FindStringSubmatch(version) + + if matches == nil { + return nil, fmt.Errorf("invalid version format") + } + + v := Version{} + + for i := 1; i < len(matches); i += 2 { + v = append(v, matches[i]) + } + + return v, nil +} + +// Return a version string like "go1.6.3". +func (v Version) String() string { + return "go" + v.Join(".") +} + +// Join version numbers with sep. +func (v Version) Join(sep string) string { + return strings.Join(([]string)(v), sep) +} + +// Compare two versions. +// Return 1 if a > b. +// Return 0 if two versions equal. +// Return -1 if a < b. +func CompareVersions(a, b Version) int { + if a == nil && b != nil { + return -1 + } + + if a != nil && b == nil { + return 1 + } + + la := len(a) + lb := len(b) + + for i := 0; i < la && i < lb; i++ { + if c := strings.Compare(a[i], b[i]); c != 0 { + return c + } + } + + if la > lb { + return 1 + } else if la < lb { + return -1 + } else { + return 0 + } +}