2014-07-09 09:37:11 -05:00
// +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 = `
2014-07-11 09:02:55 -05:00
// #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>` // no closing newline; added by writeLine()
2014-07-09 09:37:11 -05:00
func writeConstCast ( f * os . File , c string ) {
cc := c [ 2 : ] // strip leading c_
2014-07-11 09:02:55 -05:00
fmt . Fprintf ( f , "// uintptr_t %s = (uintptr_t) (%s);\n" , c , cc )
2014-07-09 09:37:11 -05:00
}
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 )
2014-07-11 09:29:52 -05:00
fmt . Fprintf ( f , "\tfmt.Fprintf(buf, \"type %%s struct {\\n\", %q)\n" , s )
fmt . Fprintf ( f , "\tfor i := 0; i < t.NumField(); i++ {\n" )
fmt . Fprintf ( f , "\t\tfmt.Fprintf(buf, \"\\t%%s %%s\\n\", t.Field(i).Name, t.Field(i).Type.Kind())\n" )
fmt . Fprintf ( f , "\t}\n" )
fmt . Fprintf ( f , "\tfmt.Fprintf(buf, \"}\\n\")\n" )
2014-07-09 09:37:11 -05:00
}
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\"" )
2014-07-11 09:02:55 -05:00
writeLine ( f , "import \"reflect\"" )
2014-07-09 09:37:11 -05:00
writeLine ( f , "import \"go/format\"" )
writeLine ( f , cgopreamble )
2014-07-10 20:10:40 -05:00
for _ , c := range consts {
writeConstCast ( f , c )
2014-07-09 09:37:11 -05:00
}
2014-07-10 20:10:40 -05:00
writeLine ( f , "import \"C\"" )
2014-07-09 09:37:11 -05:00
writeLine ( f , "func main() {" )
writeLine ( f , "\tbuf := new(bytes.Buffer)" )
2014-07-11 09:02:55 -05:00
writeLine ( f , "\tfmt.Fprintln(buf, \"package ui\")" )
2014-07-09 09:37:11 -05:00
for _ , c := range consts {
writeConstPrint ( f , c )
}
2014-07-11 09:02:55 -05:00
writeLine ( f , "\tvar t reflect.Type" )
2014-07-09 09:37:11 -05:00
for _ , s := range structs {
writeStructPrint ( f , s )
}
writeLine ( f , "\tres, err := format.Source(buf.Bytes())" )
writeLine ( f , "\tif err != nil { panic(err) }" )
2014-07-10 20:10:40 -05:00
writeLine ( f , "\tfmt.Printf(\"%s\", res)" )
2014-07-09 09:37:11 -05:00
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
}