summaryrefslogtreecommitdiff
path: root/tools/windowsconstgen.go
diff options
context:
space:
mode:
authorPietro Gagliardi <[email protected]>2014-05-25 15:36:50 -0400
committerPietro Gagliardi <[email protected]>2014-05-25 15:36:50 -0400
commit5a9a0eda6aae850b9d75ed513f6c51a1bb962b38 (patch)
treeb2055e299bc2f3b7f5695e9cd9ceb3a17fe00799 /tools/windowsconstgen.go
parent4a8f0ef902dbb77d6d4e28997db4233bb0500ec0 (diff)
Moved windowsconstgen.go out of the experiments/ folder and into a new tools/ folder.
Diffstat (limited to 'tools/windowsconstgen.go')
-rw-r--r--tools/windowsconstgen.go197
1 files changed, 197 insertions, 0 deletions
diff --git a/tools/windowsconstgen.go b/tools/windowsconstgen.go
new file mode 100644
index 0000000..448b549
--- /dev/null
+++ b/tools/windowsconstgen.go
@@ -0,0 +1,197 @@
+// 24 may 2014
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "go/token"
+ "go/ast"
+ "go/parser"
+ "sort"
+ "io/ioutil"
+ "path/filepath"
+ "os/exec"
+)
+
+func getPackage(path string) (pkg *ast.Package) {
+ fileset := token.NewFileSet() // parser.ParseDir() actually writes to this; not sure why it doesn't return one instead
+ filter := func(i os.FileInfo) bool {
+ return strings.HasSuffix(i.Name(), "_windows.go")
+ }
+ pkgs, err := parser.ParseDir(fileset, path, filter, parser.AllErrors)
+ if err != nil {
+ panic(err)
+ }
+ if len(pkgs) != 1 {
+ panic("more than one package found")
+ }
+ for k, _ := range pkgs { // get the sole key
+ pkg = pkgs[k]
+ }
+ return pkg
+}
+
+type walker struct {
+ desired func(string) bool
+}
+
+var known = map[string]string{}
+var unknown = map[string]struct{}{}
+
+func (w *walker) Visit(node ast.Node) ast.Visitor {
+ if n, ok := node.(*ast.Ident); ok {
+ if w.desired(n.Name) {
+ if n.Obj != nil {
+ delete(unknown, n.Name)
+ kind := n.Obj.Kind.String()
+ if known[n.Name] != "" && known[n.Name] != kind {
+ panic(n.Name + "(" + kind + ") already known to be a " + known[n.Name])
+ }
+ known[n.Name] = kind
+ } else if _, ok := known[n.Name]; !ok { // only if not known
+ unknown[n.Name] = struct{}{}
+ }
+ }
+ }
+ return w
+}
+
+func gatherNames(pkg *ast.Package) {
+ desired := func(name string) bool {
+ if strings.HasPrefix(name, "_") && len(name) > 1 {
+ return !strings.ContainsAny(name,
+ "abcdefghijklmnopqrstuvwxyz")
+ }
+ return false
+ }
+ for _, f := range pkg.Files {
+ for _, d := range f.Decls {
+ ast.Walk(&walker{desired}, d)
+ }
+ }
+}
+
+// some constants confuse cgo into thinking they're external symbols for some reason
+// fortunately all these constants are pointers
+// TODO debug cgo
+var hacknames = map[string]string{
+ "_INVALID_HANDLE_VALUE": "x_INVALID_HANDLE_VALUE",
+ "_NULL": "x_NULL",
+ "_IDI_APPLICATION": "x_IDI_APPLICATION",
+ "_IDC_ARROW": "x_IDC_ARROW",
+ "_HWND_MESSAGE": "x_HWND_MESSAGE",
+}
+
+func hacknamesPreamble() string {
+ if len(hacknames) == 0 {
+ return ""
+ }
+ // keep sorted for git
+ hn := make([]string, 0, len(hacknames))
+ for origname, _ := range hacknames {
+ hn = append(hn, origname)
+ }
+ sort.Strings(hn)
+ s := "// /* because cgo has issues with these */\n"
+ s += "// #include <stdint.h>\n"
+ for _, origname := range hn {
+ s += "// uintptr_t " + hacknames[origname] + " = (uintptr_t) (" +
+ origname[1:] + ");\n" // strip leading _
+ }
+ return s
+}
+
+func preamble(pkg string) string {
+ return "// autogenerated by windowsconstgen; do not edit\n" +
+ "package " + pkg + "\n"
+}
+
+func main() {
+ if len(os.Args) < 3 {
+ panic("usage: " + os.Args[0] + " path goarch [go-command-options...]")
+ }
+ pkgpath := os.Args[1]
+ targetarch := os.Args[2]
+ goopts := os.Args[3:] // valid if len(os.Args) == 3; in that case this will just be a slice of length zero
+
+ pkg := getPackage(pkgpath)
+ gatherNames(pkg)
+
+ // if we still have some known, I didn't clean things up completely
+ knowns := ""
+ for ident, kind := range known {
+ if kind != "var" && kind != "const" {
+ continue
+ }
+ knowns += "\n" + ident + " (" + kind + ")"
+ }
+ if knowns != "" {
+ panic("error: the following are still known!" + knowns) // has a newline already
+ }
+
+ // keep sorted for git
+ consts := make([]string, 0, len(unknown))
+ for ident, _ := range unknown {
+ if hackname, ok := hacknames[ident]; ok {
+ consts = append(consts, hackname)
+ continue
+ }
+ consts = append(consts, ident)
+ }
+ sort.Strings(consts)
+
+ // thanks to james4k in irc.freenode.net/#go-nuts
+ tmpdir, err := ioutil.TempDir("", "windowsconstgen")
+ if err != nil {
+ panic(err)
+ }
+ genoutname := filepath.Join(tmpdir, "gen.go")
+ f, err := os.Create(genoutname)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Fprintf(f, "%s" +
+ "import \"fmt\"\n" +
+ "// #include <windows.h>\n" +
+ "// #include <commctrl.h>\n" +
+ "%s" +
+ "import \"C\"\n" +
+ "func main() {\n" +
+ " fmt.Print(%q)\n",
+ preamble("main"), hacknamesPreamble(), preamble("ui"))
+ for _, ident := range consts {
+ if ident[0] == 'x' {
+ // hack name; strip the leading x (but not the _ after it) from the constant name but keep the value name unchanged
+ fmt.Fprintf(f, " fmt.Println(\"const %s =\", C.%s)\n", ident[1:], ident)
+ continue
+ }
+ // not a hack name; strip the leading _ from the value name but keep the constant name unchanged
+ fmt.Fprintf(f, " fmt.Println(\"const %s =\", C.%s)\n", ident, ident[1:])
+ }
+ fmt.Fprintf(f, "}\n")
+ f.Close()
+
+ cmd := exec.Command("go", "run")
+ cmd.Args = append(cmd.Args, goopts...) // valid if len(goopts) == 0; in that case this will just be a no-op
+ cmd.Args = append(cmd.Args, genoutname)
+ f, err = os.Create(filepath.Join(pkgpath, "zconstants_windows_" + targetarch + ".go"))
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ cmd.Stdout = f
+ cmd.Stderr = os.Stderr
+ cmd.Env = append(cmd.Env, os.Environ()...) // otherwise $PATH doesn't get carried over and things mysteriously fail
+ cmd.Env = append(cmd.Env,
+ "GOOS=windows",
+ "GOARCH=" + targetarch,
+ "CGO_ENABLED=1") // needed as it's not set by default in cross-compiles
+ err = cmd.Run()
+ if err != nil {
+ // TODO find a way to get the exit code
+ os.Exit(1)
+ }
+
+ // TODO remove the temporary directory
+}