From f6336617a010f0f56bcc3833e9241a1550ea246e Mon Sep 17 00:00:00 2001 From: mappu Date: Mon, 26 Aug 2024 22:45:11 +1200 Subject: [PATCH] genbindings: switch to two-phase --- cmd/genbindings/README.md | 24 +++++++++++++++++------- cmd/genbindings/intermediate.go | 1 + cmd/genbindings/main.go | 32 ++++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/cmd/genbindings/README.md b/cmd/genbindings/README.md index ffb1847..665cf1e 100644 --- a/cmd/genbindings/README.md +++ b/cmd/genbindings/README.md @@ -1,19 +1,29 @@ # genbindings -The `genbindings` program regenerates the qt bindings. +The `genbindings` program regenerates the Qt bindings. -## Archicture design +## Architecture design -Bindings are generated as follows: +Bindings are generated in two passes: + +Pass 1 1. Scan input directory for header files. 2. For each header file: 3. Run `clang --ast-dump=json` to get a JSON ast. - This is somewhat slow, the results will be cached in `./cachedir` after the first run. + - Strip all Clang AST nodes that were included from other files, to only consider the header's own definitions. 4. Convert Clang AST to our own intermediate representation. -5. Emit CABI cpp/h pair. +5. Run some transformations on the intermediate representation. +6. Cache and collect the global state of all known class names, enum names, and typedefs. + +Pass 2 + +1. For each intermediate-representation AST: +2. Emit "CABI" cpp/h pair. - The CABI is a projection of Qt into plain C. The translation unit itself is C++, but the header can be used as extern c. -6. Emit Go binding file. +3. Emit Go binding file. + - The Go binding uses CGO to call into the CABI binding. ## Configuration @@ -21,5 +31,5 @@ It's tested to work on with Debian 12 / Qt 5.15 / Clang 14 / GCC 12. You should check the following configuration: -- Input directories containing Qt headers -- All of `exceptions.go` +- `main.go`: Input directories containing Qt headers +- `exceptions.go`: Check everything diff --git a/cmd/genbindings/intermediate.go b/cmd/genbindings/intermediate.go index 8502e57..fd58fb3 100644 --- a/cmd/genbindings/intermediate.go +++ b/cmd/genbindings/intermediate.go @@ -194,6 +194,7 @@ type CppTypedef struct { } type CppParsedHeader struct { + Filename string Typedefs []CppTypedef Classes []CppClass } diff --git a/cmd/genbindings/main.go b/cmd/genbindings/main.go index 7a42be0..31d5e49 100644 --- a/cmd/genbindings/main.go +++ b/cmd/genbindings/main.go @@ -11,6 +11,10 @@ import ( "strings" ) +func cacheFilePath(inputHeader string) string { + return filepath.Join("cachedir", strings.Replace(inputHeader, `/`, `__`, -1)+".json") +} + func main() { ctx := context.Background() @@ -78,10 +82,12 @@ func main() { log.Printf("Removed %d file(s).", cleaned) } + var processHeaders []*CppParsedHeader + for _, inputHeader := range includeFiles { // If we have a cached clang AST, use that instead - cacheFile := filepath.Join("cachedir", strings.Replace(inputHeader, `/`, `__`, -1)+".json") + cacheFile := cacheFilePath(inputHeader) astJson, err := ioutil.ReadFile(cacheFile) var astInner []interface{} = nil if err != nil { @@ -123,11 +129,23 @@ func main() { panic(err) } + parsed.Filename = inputHeader // Stash + // AST transforms on our IL astTransformChildClasses(parsed) // must be first astTransformOptional(parsed) astTransformOverloads(parsed) + processHeaders = append(processHeaders, parsed) + } + + // + // PASS 2 + // + + for _, parsed := range processHeaders { + + log.Printf("Processing %q...", parsed.Filename) { // Save the IL file for debug inspection jb, err := json.MarshalIndent(parsed, "", "\t") @@ -135,7 +153,7 @@ func main() { panic(err) } - err = ioutil.WriteFile(cacheFile+".ours.json", jb, 0644) + err = ioutil.WriteFile(cacheFilePath(parsed.Filename)+".ours.json", jb, 0644) if err != nil { panic(err) } @@ -148,9 +166,9 @@ func main() { } // Emit 3 code files from the intermediate format - outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(inputHeader), `.h`)) + outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(parsed.Filename), `.h`)) - goSrc, err := emitGo(parsed, filepath.Base(inputHeader)) + goSrc, err := emitGo(parsed, filepath.Base(parsed.Filename)) if err != nil { panic(err) } @@ -160,7 +178,7 @@ func main() { panic(err) } - bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(inputHeader)) + bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(parsed.Filename)) if err != nil { panic(err) } @@ -170,7 +188,7 @@ func main() { panic(err) } - bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(inputHeader)) + bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(parsed.Filename)) if err != nil { panic(err) } @@ -182,8 +200,6 @@ func main() { // Done - log.Printf("Processing %q completed", inputHeader) - } log.Printf("Processing %d file(s) completed", len(includeFiles))