diff --git a/redo/zwinconstgen.go b/redo/zwinconstgen.go new file mode 100644 index 0000000..1df9bd9 --- /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 +#include +#include +` + +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 _, const := range consts { + writeConstCast(f, const) + } + 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) }") + wrtieLine(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 +}