2014-05-24 22:44:28 -05:00
// 24 may 2014
package main
import (
2014-05-25 00:13:16 -05:00
"fmt"
2014-05-24 22:44:28 -05:00
"os"
"strings"
"go/token"
"go/ast"
"go/parser"
2014-05-25 00:28:59 -05:00
"sort"
2014-05-25 02:13:49 -05:00
"io/ioutil"
"path/filepath"
"os/exec"
2014-05-24 22:44:28 -05:00
)
2014-05-25 00:13:16 -05:00
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
2014-05-24 22:44:28 -05:00
filter := func ( i os . FileInfo ) bool {
return strings . HasSuffix ( i . Name ( ) , "_windows.go" )
}
2014-05-25 00:13:16 -05:00
pkgs , err := parser . ParseDir ( fileset , path , filter , parser . AllErrors )
2014-05-24 22:44:28 -05:00
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 ]
}
2014-05-25 00:13:16 -05:00
return pkg
2014-05-24 23:31:40 -05:00
}
2014-05-24 22:44:28 -05:00
2014-05-24 23:31:40 -05:00
type walker struct {
desired func ( string ) bool
}
2014-05-24 23:40:22 -05:00
var known = map [ string ] string { }
var unknown = map [ string ] struct { } { }
2014-05-24 23:31:40 -05:00
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 {
2014-05-24 23:40:22 -05:00
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 { } { }
2014-05-24 22:44:28 -05:00
}
}
}
2014-05-24 23:31:40 -05:00
return w
}
2014-05-25 00:13:16 -05:00
func gatherNames ( pkg * ast . Package ) {
2014-05-24 23:31:40 -05:00
desired := func ( name string ) bool {
if strings . HasPrefix ( name , "_" ) && len ( name ) > 1 {
return ! strings . ContainsAny ( name ,
"abcdefghijklmnopqrstuvwxyz" )
2014-05-24 22:44:28 -05:00
}
2014-05-24 23:31:40 -05:00
return false
2014-05-24 22:44:28 -05:00
}
for _ , f := range pkg . Files {
2014-05-24 23:31:40 -05:00
for _ , d := range f . Decls {
ast . Walk ( & walker { desired } , d )
}
2014-05-24 22:44:28 -05:00
}
}
2014-05-25 00:13:16 -05:00
2014-05-25 13:04:03 -05:00
// some constants confuse cgo into thinking they're external symbols for some reason
2014-05-25 14:09:01 -05:00
// fortunately all these constants are pointers
2014-05-25 13:04:03 -05:00
// TODO debug cgo
var hacknames = map [ string ] string {
"_INVALID_HANDLE_VALUE" : "x_INVALID_HANDLE_VALUE" ,
2014-05-25 14:09:01 -05:00
"_NULL" : "x_NULL" ,
"_IDI_APPLICATION" : "x_IDI_APPLICATION" ,
"_IDC_ARROW" : "x_IDC_ARROW" ,
2014-05-25 14:23:11 -05:00
"_HWND_MESSAGE" : "x_HWND_MESSAGE" ,
2014-05-25 13:04:03 -05:00
}
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
}
2014-05-25 00:28:59 -05:00
func preamble ( pkg string ) string {
return "// autogenerated by windowsconstgen; do not edit\n" +
"package " + pkg + "\n"
}
2014-05-30 13:02:57 -05:00
// 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 {
2014-05-30 16:59:29 -05:00
"386" : "etWindowLongW" ,
"amd64" : "etWindowLongPtrW" ,
2014-05-30 13:02:57 -05:00
}
func printConst ( f * os . File , goconst string , winconst string ) {
fmt . Fprintf ( f , " fmt.Println(\"const %s =\", C.%s)\n" , goconst , winconst )
}
func printGWLPName ( f * os . File , which string , char string , targetarch string ) {
fmt . Fprintf ( f , " fmt.Println(\"var %s = user32.NewProc(\\\"%s\\\")\")\n" ,
which , char + gwlpNames [ targetarch ] )
}
2014-05-25 00:13:16 -05:00
func main ( ) {
2014-05-25 12:18:45 -05:00
if len ( os . Args ) < 3 {
panic ( "usage: " + os . Args [ 0 ] + " path goarch [go-command-options...]" )
2014-05-25 00:13:16 -05:00
}
2014-05-25 02:13:49 -05:00
pkgpath := os . Args [ 1 ]
targetarch := os . Args [ 2 ]
2014-05-30 13:02:57 -05:00
if _ , ok := gwlpNames [ targetarch ] ; ! ok {
panic ( "unknown target windows/" + targetarch )
}
2014-05-25 12:18:45 -05:00
goopts := os . Args [ 3 : ] // valid if len(os.Args) == 3; in that case this will just be a slice of length zero
2014-05-25 00:13:16 -05:00
2014-05-25 02:13:49 -05:00
pkg := getPackage ( pkgpath )
2014-05-25 00:13:16 -05:00
gatherNames ( pkg )
2014-05-25 12:02:56 -05:00
// 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
2014-05-25 00:13:16 -05:00
}
2014-05-25 12:02:56 -05:00
knowns += "\n" + ident + " (" + kind + ")"
}
if knowns != "" {
2014-05-25 14:23:11 -05:00
panic ( "error: the following are still known!" + knowns ) // has a newline already
2014-05-25 00:13:16 -05:00
}
2014-05-25 00:28:59 -05:00
// keep sorted for git
2014-05-25 12:02:56 -05:00
consts := make ( [ ] string , 0 , len ( unknown ) )
for ident , _ := range unknown {
2014-05-25 13:04:03 -05:00
if hackname , ok := hacknames [ ident ] ; ok {
consts = append ( consts , hackname )
continue
}
2014-05-25 12:02:56 -05:00
consts = append ( consts , ident )
2014-05-25 10:35:55 -05:00
}
2014-05-25 00:28:59 -05:00
sort . Strings ( consts )
2014-05-25 02:15:49 -05:00
// thanks to james4k in irc.freenode.net/#go-nuts
2014-05-25 02:13:49 -05:00
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" +
2014-05-25 00:28:59 -05:00
"import \"fmt\"\n" +
"// #include <windows.h>\n" +
2014-05-25 02:13:49 -05:00
"// #include <commctrl.h>\n" +
2014-05-25 13:04:03 -05:00
"%s" +
2014-05-25 00:28:59 -05:00
"import \"C\"\n" +
"func main() {\n" +
2014-05-25 13:05:23 -05:00
" fmt.Print(%q)\n" ,
2014-05-25 13:04:03 -05:00
preamble ( "main" ) , hacknamesPreamble ( ) , preamble ( "ui" ) )
2014-05-25 00:28:59 -05:00
for _ , ident := range consts {
2014-05-25 13:04:03 -05:00
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
2014-05-30 13:02:57 -05:00
printConst ( f , ident [ 1 : ] , ident )
2014-05-25 13:04:03 -05:00
continue
}
// not a hack name; strip the leading _ from the value name but keep the constant name unchanged
2014-05-30 13:02:57 -05:00
printConst ( f , ident , ident [ 1 : ] )
2014-05-25 00:13:16 -05:00
}
2014-05-30 13:02:57 -05:00
// and now for _getWindowLongPtr/_setWindowLongPtr
printGWLPName ( f , "_getWindowLongPtr" , "G" , targetarch )
printGWLPName ( f , "_setWindowLongPtr" , "S" , targetarch )
2014-05-25 02:13:49 -05:00
fmt . Fprintf ( f , "}\n" )
f . Close ( )
2014-05-25 12:18:45 -05:00
cmd := exec . Command ( "go" , "run" )
2014-05-25 12:20:19 -05:00
cmd . Args = append ( cmd . Args , goopts ... ) // valid if len(goopts) == 0; in that case this will just be a no-op
2014-05-25 12:18:45 -05:00
cmd . Args = append ( cmd . Args , genoutname )
2014-05-25 02:13:49 -05:00
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
2014-05-25 15:50:52 -05:00
// 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 )
}
2014-05-25 02:13:49 -05:00
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
2014-05-25 00:13:16 -05:00
}