summaryrefslogtreecommitdiff
path: root/redo/zwinconstgen.go
diff options
context:
space:
mode:
Diffstat (limited to 'redo/zwinconstgen.go')
-rw-r--r--redo/zwinconstgen.go230
1 files changed, 230 insertions, 0 deletions
diff --git a/redo/zwinconstgen.go b/redo/zwinconstgen.go
new file mode 100644
index 0000000..f4f0e9f
--- /dev/null
+++ b/redo/zwinconstgen.go
@@ -0,0 +1,230 @@
+// +build ignore
+
+// 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 {
+ switch n := node.(type) {
+ case *ast.Ident: // constant or structure?
+ 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{}{}
+ }
+ }
+ case *ast.Comment: // function?
+ // TODO
+ }
+ return w
+}
+
+func gatherNames(pkg *ast.Package) {
+ desired := func(name string) bool {
+ return strings.HasPrefix(name, "c_") || // constants
+ strings.HasPrefix(name, "s_") // structs
+ }
+ for _, f := range pkg.Files {
+ for _, d := range f.Decls {
+ ast.Walk(&walker{desired}, d)
+ }
+ }
+}
+
+// for backwards compatibiilty reasons, Windows defines GetWindowLongPtr()/SetWindowLongPtr() as a macro which expands to GetWindowLong()/SetWindowLong() on 32-bit systems
+// we'll just simulate that here
+var gwlpNames = map[string]string{
+ "386": "etWindowLongW",
+ "amd64": "etWindowLongPtrW",
+}
+
+func writeLine(f *os.File, line string) {
+ fmt.Fprintf(f, "%s\n", line)
+}
+
+const cgopreamble = `
+#define UNICODE
+#define _UNICODE
+#define STRICT
+#define STRICT_TYPED_ITEMIDS
+/* get Windows version right; right now Windows XP */
+#define WINVER 0x0501
+#define _WIN32_WINNT 0x0501
+#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
+#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
+#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
+#include <windows.h>
+#include <commctrl.h>
+#include <stdint.h>
+`
+
+func writeConstCast(f *os.File, c string) {
+ cc := c[2:] // strip leading c_
+ fmt.Fprintf(f, "uintptr_t %s = (uintptr_t) (%s);\n", c, cc)
+}
+
+func writeConstPrint(f *os.File, c string) {
+ fmt.Fprintf(f, "\tfmt.Fprintf(buf, \"const %%s = %%d\\n\", %q, C.%s)\n", c, c)
+}
+
+func writeStructPrint(f *os.File, s string) {
+ cs := s[2:] // strip leading s_
+ fmt.Fprintf(f, "\tt = reflect.TypeOf(C.%s{})\n", cs)
+ // TODO
+}
+
+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]
+ if _, ok := gwlpNames[targetarch]; !ok {
+ panic("unknown target windows/" + targetarch)
+ }
+ 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
+ if len(known) > 0 {
+ knowns := ""
+ for ident, kind := range known {
+ if kind != "var" && kind != "const" {
+ continue
+ }
+ knowns += "\n" + ident + " (" + kind + ")"
+ }
+ panic("error: the following are still known!" + knowns) // has a newline already
+ }
+
+ // keep sorted for git
+ consts := make([]string, 0, len(unknown))
+ structs := make([]string, 0, len(unknown))
+ for ident, _ := range unknown {
+ if strings.HasPrefix(ident, "s_") {
+ structs = append(structs, ident)
+ continue
+ }
+ consts = append(consts, ident)
+ }
+ sort.Strings(consts)
+ sort.Strings(structs)
+
+ // 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)
+ }
+
+ writeLine(f, "package main")
+ writeLine(f, "import \"fmt\"")
+ writeLine(f, "import \"bytes\"")
+ writeLine(f, "import \"runtime\"")
+ writeLine(f, "import \"go/format\"")
+ writeLine(f, "/*")
+ writeLine(f, cgopreamble)
+ for _, c := range consts {
+ writeConstCast(f, c)
+ }
+ writeLine(f, "*/")
+ writeLine(f, "import \"C\"")
+ writeLine(f, "func main() {")
+ writeLine(f, "\tbuf := new(bytes.Buffer)")
+ writeLine(f, "\tfmt.Fprintln(buf, \"package main\")")
+ writeLine(f, "\tfmt.Fprintln(buf, \"func main() {\")")
+ for _, c := range consts {
+ writeConstPrint(f, c)
+ }
+ writeLine(f, "\tfmt.Fprintln(buf, \"var t reflect.Type\")")
+ writeLine(f, "\tfmt.Fprintln(buf, \"var s string\")")
+ for _, s := range structs {
+ writeStructPrint(f, s)
+ }
+ writeLine(f, "\tfmt.Fprintln(buf, \"}\")")
+ writeLine(f, "\tres, err := format.Source(buf.Bytes())")
+ writeLine(f, "\tif err != nil { panic(err) }")
+ writeLine(f, "\tfmt.Printf(\"%s\", res)")
+ writeLine(f, "}")
+
+ 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
+ // we need to preserve the environment EXCEPT FOR the variables we're overriding
+ // thanks to raggi and smw in irc.freenode.net/#go-nuts
+ for _, ev := range os.Environ() {
+ if strings.HasPrefix(ev, "GOOS=") ||
+ strings.HasPrefix(ev, "GOARCH=") ||
+ strings.HasPrefix(ev, "CGO_ENABLED=") {
+ continue
+ }
+ cmd.Env = append(cmd.Env, ev)
+ }
+ 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
+}